From c64b4fae354096719d790ec41785bcdcf4ac1c25 Mon Sep 17 00:00:00 2001 From: synesissoftware Date: Mon, 31 Aug 2026 21:37:32 +1000 Subject: [PATCH] v0.1 (#1) * v1 * 0.1.2 (#2) * Modernise Pantheios.Extras.AtExit version macros for 0.1.2-alpha1. Replace the legacy named-constant PANTHEIOS_EXTRAS_ATEXIT_VER with the computed MAJOR/MINOR/PATCH/ALPHABETA form used by BDUT and WilliamsCRC. * .vimrc * Add AtExit CTest/Windows runners and tidy C source basics. Bring helper parity with ctest_cmake.sh and run_all_unit_tests.cmd, and align EOF blank lines plus the unit-test argv form with gold C libraries. * Record AtExit functionality-review items in TODO.md. Capture high/medium/low contract, locking, and test gaps from the in-depth review so they are tracked before implementation. * Align AtExit scratch naming and park remaining helper/API work. Rename the scratch program to main.c, and record the MSYSTEM/MinGW helper deferral plus the postponed functionality findings in TODO.md. * fix(ci): compiler compatibilities * 0.1.3 (#3) * Modernise Pantheios.Extras.AtExit version macros for 0.1.2-alpha1. Replace the legacy named-constant PANTHEIOS_EXTRAS_ATEXIT_VER with the computed MAJOR/MINOR/PATCH/ALPHABETA form used by BDUT and WilliamsCRC. * .vimrc * Add AtExit CTest/Windows runners and tidy C source basics. Bring helper parity with ctest_cmake.sh and run_all_unit_tests.cmd, and align EOF blank lines plus the unit-test argv form with gold C libraries. * Record AtExit functionality-review items in TODO.md. Capture high/medium/low contract, locking, and test gaps from the in-depth review so they are tracked before implementation. * Align AtExit scratch naming and park remaining helper/API work. Rename the scratch program to main.c, and record the MSYSTEM/MinGW helper deferral plus the postponed functionality findings in TODO.md. * Retarget Pantheios.Extras.AtExit to 0.1.3 for 23rd August 2026. Ship the current work as a release (0xFF alphabeta) rather than 0.1.2-alpha1, and align CHANGES.md and NEWS.md to that date. * Cover AtExit add failures in the example and extend unit tests. Check pantheios_extras_atexit_add results in example.c.1, and add coverage for add-without-init, add-after-uninit, and version macros. * Explain in the AtExit README why libc atexit is not enough. Describe the missing parameter, scarce shared slots, and lack of unregister, and how one hook plus a LIFO list addresses that. * squash-commit * chore * fix * refactor * fix --- .gitattributes | 17 + .github/workflows/ci-cell.yml | 332 ++++++++++++++++++ .github/workflows/ci.yml | 207 +++++++++++ .gitignore | 242 +++++++++++++ .sis/project_name.txt | 1 + .sis/script_info_lines.txt | 3 + .vimrc | 70 ++++ .vscode/settings.json | 182 ++++++++++ AUTHORS.md | 21 ++ CHANGES.md | 32 ++ CMakeLists.txt | 279 +++++++++++++++ FAQ.md | 63 ++++ INSTALL.md | 154 ++++++++ LICENSE | 33 ++ NEWS.md | 18 + README.md | 127 +++++++ TODO.md | 45 +++ build_cmake.sh | 125 +++++++ clean_cmake.sh | 87 +++++ cmake/BuildType.cmake | 61 ++++ cmake/LanguageFullVersion.cmake | 63 ++++ cmake/TargetMacros.cmake | 104 ++++++ cmake/pantheios.extras.atexit-config.cmake.in | 2 + ctest_cmake.sh | 108 ++++++ examples/CMakeLists.txt | 33 ++ examples/c/CMakeLists.txt | 4 + examples/c/example.c.1/CMakeLists.txt | 1 + examples/c/example.c.1/example.c.1.c | 104 ++++++ include/pantheios/extras/atexit.h | 79 +++++ include/pantheios/extras/atexit/atexit.h | 192 ++++++++++ prepare_cmake.sh | 193 ++++++++++ remove_cmake_artefacts.sh | 153 ++++++++ run_all_examples.sh | 132 +++++++ run_all_scratch_tests.sh | 137 ++++++++ run_all_unit_tests.cmd | 188 ++++++++++ run_all_unit_tests.sh | 200 +++++++++++ src/CMakeLists.txt | 89 +++++ src/atexit.api.c | 265 ++++++++++++++ test/CMakeLists.txt | 34 ++ test/scratch/CMakeLists.txt | 4 + .../test.scratch.atexit/CMakeLists.txt | 1 + test/scratch/test.scratch.atexit/main.c | 58 +++ test/unit/CMakeLists.txt | 5 + test/unit/test.unit.atexit.api/CMakeLists.txt | 1 + .../test.unit.atexit.api.c | 163 +++++++++ test/unit/test.unit.version/CMakeLists.txt | 1 + .../test.unit.version/test.unit.version.c | 68 ++++ 47 files changed, 4481 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/ci-cell.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .sis/project_name.txt create mode 100644 .sis/script_info_lines.txt create mode 100644 .vimrc create mode 100644 .vscode/settings.json create mode 100644 AUTHORS.md create mode 100644 CHANGES.md create mode 100644 CMakeLists.txt create mode 100644 FAQ.md create mode 100644 INSTALL.md create mode 100644 LICENSE create mode 100644 NEWS.md create mode 100644 TODO.md create mode 100755 build_cmake.sh create mode 100755 clean_cmake.sh create mode 100644 cmake/BuildType.cmake create mode 100644 cmake/LanguageFullVersion.cmake create mode 100644 cmake/TargetMacros.cmake create mode 100644 cmake/pantheios.extras.atexit-config.cmake.in create mode 100755 ctest_cmake.sh create mode 100644 examples/CMakeLists.txt create mode 100644 examples/c/CMakeLists.txt create mode 100644 examples/c/example.c.1/CMakeLists.txt create mode 100644 examples/c/example.c.1/example.c.1.c create mode 100644 include/pantheios/extras/atexit.h create mode 100644 include/pantheios/extras/atexit/atexit.h create mode 100755 prepare_cmake.sh create mode 100755 remove_cmake_artefacts.sh create mode 100755 run_all_examples.sh create mode 100755 run_all_scratch_tests.sh create mode 100644 run_all_unit_tests.cmd create mode 100755 run_all_unit_tests.sh create mode 100644 src/CMakeLists.txt create mode 100644 src/atexit.api.c create mode 100644 test/CMakeLists.txt create mode 100644 test/scratch/CMakeLists.txt create mode 100644 test/scratch/test.scratch.atexit/CMakeLists.txt create mode 100644 test/scratch/test.scratch.atexit/main.c create mode 100644 test/unit/CMakeLists.txt create mode 100644 test/unit/test.unit.atexit.api/CMakeLists.txt create mode 100644 test/unit/test.unit.atexit.api/test.unit.atexit.api.c create mode 100644 test/unit/test.unit.version/CMakeLists.txt create mode 100644 test/unit/test.unit.version/test.unit.version.c diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e6605d1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +*.c linguist-language=C +*.cmake linguist-language=CMake +*.cmd linguist-detectable=false +*.cpp linguist-language=C++ +*.cxx linguist-language=C++ +*.dsp linguist-detectable=false +*.dsw linguist-detectable=false +*.h linguist-language=C +*.hpp linguist-language=C++ +*.html linguist-detectable=false +*.hxx linguist-language=C++ +*.sh linguist-detectable=false +*.sln linguist-detectable=false +*.vcxproj linguist-detectable=false +*.vimrc linguist-detectable=false +CMakeLists.txt linguist-language=CMake +makefile linguist-detectable=false diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml new file mode 100644 index 0000000..dc1f587 --- /dev/null +++ b/.github/workflows/ci-cell.yml @@ -0,0 +1,332 @@ +name: CI cell + +on: + workflow_call: + inputs: + cell-id: + required: true + type: string + os: + required: true + type: string + c-compiler: + required: true + type: string + cpp-compiler: + required: true + type: string + build-type: + required: false + type: string + default: Release + +jobs: + build: + name: Build (${{ inputs.cell-id }}) + runs-on: ${{ inputs.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Install Clang (Ubuntu) + if: runner.os == 'Linux' && inputs.c-compiler == 'clang' + run: sudo apt-get update && sudo apt-get install -y clang + + - name: Setup MSYS2 MinGW + if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: >- + mingw-w64-x86_64-toolchain + mingw-w64-x86_64-cmake + mingw-w64-x86_64-make + + - name: Install test dependencies (STLSoft + shwild + xTests) + shell: bash + run: | + set -euxo pipefail + + DEPS="${RUNNER_TEMP}/sis-deps" + STLSRC="${RUNNER_TEMP}/stlsoft-src" + STLBUILD="${RUNNER_TEMP}/stlsoft-build" + SHWSRC="${RUNNER_TEMP}/shwild-src" + SHWBUILD="${RUNNER_TEMP}/shwild-build" + XTSRC="${RUNNER_TEMP}/xtests-src" + XTBUILD="${RUNNER_TEMP}/xtests-build" + + mkdir -p "$DEPS" + + # 1. STLSoft + git clone --depth 1 https://github.com/synesissoftware/STLSoft "$STLSRC" + if [ "${{ inputs.c-compiler }}" = "cl" ]; then + cmake -S "$STLSRC" -B "$STLBUILD" \ + -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$STLBUILD" --config "${{ inputs.build-type }}" --parallel + cmake --install "$STLBUILD" --config "${{ inputs.build-type }}" + elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + cmake -S "$STLSRC" -B "$STLBUILD" -G "MinGW Makefiles" \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$STLBUILD" --parallel 2 + cmake --install "$STLBUILD" + else + cmake -S "$STLSRC" -B "$STLBUILD" \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ + -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$STLBUILD" --parallel + cmake --install "$STLBUILD" + fi + + # 2. shwild + git clone --depth 1 https://github.com/synesissoftware/shwild "$SHWSRC" + if [ "${{ inputs.c-compiler }}" = "cl" ]; then + cmake -S "$SHWSRC" -B "$SHWBUILD" \ + -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DCMAKE_PREFIX_PATH="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$SHWBUILD" --config "${{ inputs.build-type }}" --parallel + cmake --install "$SHWBUILD" --config "${{ inputs.build-type }}" + elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + cmake -S "$SHWSRC" -B "$SHWBUILD" -G "MinGW Makefiles" \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DCMAKE_PREFIX_PATH="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$SHWBUILD" --parallel 2 + cmake --install "$SHWBUILD" + else + cmake -S "$SHWSRC" -B "$SHWBUILD" \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ + -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DCMAKE_PREFIX_PATH="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$SHWBUILD" --parallel + cmake --install "$SHWBUILD" + fi + + # 3. xTests + git clone --depth 1 https://github.com/synesissoftware/xTests "$XTSRC" + if [ "${{ inputs.c-compiler }}" = "cl" ]; then + cmake -S "$XTSRC" -B "$XTBUILD" \ + -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DCMAKE_PREFIX_PATH="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$XTBUILD" --config "${{ inputs.build-type }}" --parallel + cmake --install "$XTBUILD" --config "${{ inputs.build-type }}" + elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + cmake -S "$XTSRC" -B "$XTBUILD" -G "MinGW Makefiles" \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DCMAKE_PREFIX_PATH="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$XTBUILD" --parallel 2 + cmake --install "$XTBUILD" + else + cmake -S "$XTSRC" -B "$XTBUILD" \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ + -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ + -DCMAKE_INSTALL_PREFIX="$DEPS" \ + -DCMAKE_PREFIX_PATH="$DEPS" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + cmake --build "$XTBUILD" --parallel + cmake --install "$XTBUILD" + fi + + - name: Configure + shell: bash + run: | + set -euo pipefail + if [ "${{ inputs.c-compiler }}" = "cl" ]; then + cmake -B build -S . \ + -DCMAKE_DISABLE_FIND_PACKAGE_MFC=TRUE \ + -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_TESTING=ON + elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + cmake -B build -G "MinGW Makefiles" \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_C_COMPILER=gcc \ + -DCMAKE_CXX_COMPILER=g++ \ + -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_TESTING=ON + else + cmake -B build -S . \ + -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ + -DCMAKE_C_COMPILER="${{ inputs.c-compiler }}" \ + -DCMAKE_CXX_COMPILER="${{ inputs.cpp-compiler }}" \ + -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/sis-deps" \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_TESTING=ON + fi + + - name: Build + shell: bash + run: | + set -euo pipefail + if [ "${{ inputs.c-compiler }}" = "cl" ]; then + cmake --build build --config "${{ inputs.build-type }}" --parallel + elif [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + cmake --build build --parallel 2 + else + cmake --build build --parallel + fi + + - name: Upload build tree + uses: actions/upload-artifact@v4 + with: + name: sis-build-${{ inputs.cell-id }} + path: build + retention-days: 1 + + run-examples: + name: Examples (${{ inputs.cell-id }}) + needs: build + runs-on: ${{ inputs.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup MSYS2 MinGW + if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: mingw-w64-x86_64-toolchain + + - name: Download build tree + uses: actions/download-artifact@v4 + with: + name: sis-build-${{ inputs.cell-id }} + path: build + + - name: Run examples + shell: bash + run: | + set -euo pipefail + if [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + fi + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_examples.sh -M + + test-unit: + name: Unit tests (${{ inputs.cell-id }}) + needs: build + runs-on: ${{ inputs.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup MSYS2 MinGW + if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: mingw-w64-x86_64-toolchain + + - name: Download build tree + uses: actions/download-artifact@v4 + with: + name: sis-build-${{ inputs.cell-id }} + path: build + + - name: Run unit tests + shell: bash + run: | + set -euo pipefail + if [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + fi + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_unit_tests.sh -M --unit-only + + test-component: + name: Component tests (${{ inputs.cell-id }}) + needs: build + runs-on: ${{ inputs.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup MSYS2 MinGW + if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: mingw-w64-x86_64-toolchain + + - name: Download build tree + uses: actions/download-artifact@v4 + with: + name: sis-build-${{ inputs.cell-id }} + path: build + + - name: Run component tests + shell: bash + run: | + set -euo pipefail + if [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + fi + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_unit_tests.sh -M --component-only + + test-scratch: + name: Scratch tests (${{ inputs.cell-id }}) + needs: build + continue-on-error: true + runs-on: ${{ inputs.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup MSYS2 MinGW + if: runner.os == 'Windows' && inputs.c-compiler == 'mingw' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: mingw-w64-x86_64-toolchain + + - name: Download build tree + uses: actions/download-artifact@v4 + with: + name: sis-build-${{ inputs.cell-id }} + path: build + + - name: Run scratch tests + shell: bash + run: | + set -euo pipefail + if [ "${{ inputs.c-compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + fi + SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./run_all_scratch_tests.sh -M diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..68f2398 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,207 @@ +name: CI + +on: + push: + branches: + - master + - dev + - boilerplate + - idiomatic + - rc1 + - rc2 + - rc3 + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + BUILD_TYPE: Release + +jobs: + cell: + name: CI (${{ matrix.id }}) + strategy: + fail-fast: false + matrix: + include: + - id: linux-clang + os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + - id: linux-gcc + os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - id: macos-clang + os: macos-latest + c_compiler: clang + cpp_compiler: clang++ + - id: macos-gcc + os: macos-latest + c_compiler: gcc + cpp_compiler: g++ + - id: windows-cl + os: windows-latest + c_compiler: cl + cpp_compiler: cl + - id: windows-mingw + os: windows-latest + c_compiler: mingw + cpp_compiler: g++ + uses: ./.github/workflows/ci-cell.yml + with: + cell-id: ${{ matrix.id }} + os: ${{ matrix.os }} + c-compiler: ${{ matrix.c_compiler }} + cpp-compiler: ${{ matrix.cpp_compiler }} + build-type: Release + secrets: inherit + + install-smoke: + name: Install smoke (${{ matrix.os }}, ${{ matrix.c_compiler }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + - os: macos-latest + c_compiler: clang + cpp_compiler: clang++ + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: windows-latest + c_compiler: mingw + cpp_compiler: g++ + + steps: + - uses: actions/checkout@v4 + + - name: Install Clang (Ubuntu) + if: runner.os == 'Linux' && matrix.c_compiler == 'clang' + run: sudo apt-get update && sudo apt-get install -y clang + + - name: Setup MSYS2 MinGW + if: runner.os == 'Windows' && matrix.c_compiler == 'mingw' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: >- + mingw-w64-x86_64-toolchain + mingw-w64-x86_64-cmake + mingw-w64-x86_64-make + + - name: Configure + shell: bash + run: | + set -euo pipefail + if [ "${{ matrix.c_compiler }}" = "cl" ]; then + cmake -B build -S . \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + elif [ "${{ matrix.c_compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + cmake -B build -G "MinGW Makefiles" \ + -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" \ + -DCMAKE_C_COMPILER=gcc \ + -DCMAKE_CXX_COMPILER=g++ \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + else + cmake -B build -S . \ + -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" \ + -DCMAKE_C_COMPILER="${{ matrix.c_compiler }}" \ + -DCMAKE_CXX_COMPILER="${{ matrix.cpp_compiler }}" \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_TESTING=OFF + fi + + - name: Build + shell: bash + run: | + set -euo pipefail + if [ "${{ matrix.c_compiler }}" = "cl" ]; then + cmake --build build --config "${{ env.BUILD_TYPE }}" --parallel + elif [ "${{ matrix.c_compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + cmake --build build --parallel 2 + else + cmake --build build --parallel + fi + + - name: Install + shell: bash + run: | + set -euo pipefail + PREFIX="${RUNNER_TEMP}/install-prefix" + echo "INSTALL_PREFIX=$PREFIX" >> "$GITHUB_ENV" + if [ "${{ matrix.c_compiler }}" = "cl" ]; then + cmake --install build --config "${{ env.BUILD_TYPE }}" --prefix "$PREFIX" + else + cmake --install build --prefix "$PREFIX" + fi + + - name: Verify install tree + shell: bash + run: | + set -euo pipefail + test -f "${INSTALL_PREFIX}/include/pantheios/extras/atexit.h" + test -f "${INSTALL_PREFIX}/include/pantheios/extras/atexit/atexit.h" + test -f "${INSTALL_PREFIX}/lib/cmake/pantheios.extras.atexit/pantheios.extras.atexit-config.cmake" + + - name: Configure and run install smoke consumer + shell: bash + run: | + set -euo pipefail + CONSUMER="${RUNNER_TEMP}/consumer" + mkdir -p "$CONSUMER" + cat > "$CONSUMER/CMakeLists.txt" <<'EOF' + cmake_minimum_required(VERSION 3.20) + project(atexit_install_smoke C) + find_package(Pantheios.Extras.AtExit REQUIRED) + add_executable(consumer consumer.c) + target_link_libraries(consumer PRIVATE Pantheios.Extras.AtExit::core) + EOF + cat > "$CONSUMER/consumer.c" <<'EOF' + #include + #include + int main(void) + { + if (0 != pantheios_extras_atexit_init(NULL, 0)) + { + return 1; + } + pantheios_extras_atexit_uninit(); + return 0; + } + EOF + if [ "${{ matrix.c_compiler }}" = "cl" ]; then + cmake -S "$CONSUMER" -B consumer-build \ + -DCMAKE_PREFIX_PATH="${INSTALL_PREFIX}" + cmake --build consumer-build --config "${{ env.BUILD_TYPE }}" --parallel + consumer-build/${{ env.BUILD_TYPE }}/consumer.exe + elif [ "${{ matrix.c_compiler }}" = "mingw" ]; then + export PATH="/mingw64/bin:$PATH" + cmake -S "$CONSUMER" -B consumer-build -G "MinGW Makefiles" \ + -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" \ + -DCMAKE_PREFIX_PATH="${INSTALL_PREFIX}" + cmake --build consumer-build --parallel 2 + consumer-build/consumer.exe + else + cmake -S "$CONSUMER" -B consumer-build \ + -DCMAKE_BUILD_TYPE="${{ env.BUILD_TYPE }}" \ + -DCMAKE_C_COMPILER="${{ matrix.c_compiler }}" \ + -DCMAKE_CXX_COMPILER="${{ matrix.cpp_compiler }}" \ + -DCMAKE_PREFIX_PATH="${INSTALL_PREFIX}" + cmake --build consumer-build --parallel + consumer-build/consumer + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68e4424 --- /dev/null +++ b/.gitignore @@ -0,0 +1,242 @@ +# Synesis Information Systems .gitignore for C/C++ libraries + + +# directories (by name) + +/.git-old/ +/.vs/ + +.svn/ + +.classpath +/.idea/ +/__pycache__ +/node_modules + +/_build/ +/_build-ci/ +/_internal/ +/_analyses/ + +/bin/ +/ipch/ +/lib/ +bin/ +ipch/ +lib/ + +/doc/ +/docs/ +/dox/ +/dox +/ideas/ +ideas/ +/included/ +/releases/ +/scratch/ +/temp/ + +/--include +/--src +/-include +/-src + +/examples/bin +/examples/c/bin +/examples/cpp/bin + +/examples/lib +/examples/c/lib +/examples/cpp/lib + +projects/util/vc10-generated/ + +perf_test/ + +scripts/internal/ + +/include/shwild/ +/include/xcontract/ +/include/xcover/ +/include/xtests/ +/src/shwild/ +/src/xcontract/ +/src/xcover/ +/src/xtests/ + +Debug Multithreaded DLL/ +Debug Multithreaded Dll/ +Debug Multithreaded pseudoUNIX/ +Debug Multithreaded pseudoUnix/ +Debug Multithreaded/ +Debug/ +DebugDLL/ +DebugDLLUNIX/ +DebugDll/ +DebugMTDLL/ +DebugMTDll/ +DebugMTUnix/ +DebugMt/ +DebugNox/ +DebugUNIX/ +DebugUnix/ +Release Multithreaded DLL/ +Release Multithreaded Dll/ +Release Multithreaded pseudoUNIX/ +Release Multithreaded pseudoUnix/ +Release Multithreaded/ +Release/ +ReleaseDLL/ +ReleaseDLLUNIX/ +ReleaseDll/ +ReleaseMTDLL/ +ReleaseMTDll/ +ReleaseMTUnix/ +ReleaseMt/ +ReleaseNox/ +ReleaseUnix/ +UDebug/ +UDebugMt/ +URelease/ +UReleaseMt/ +Unicode Debug Multithreaded DLL/ +Unicode Debug Multithreaded Dll/ +Unicode Debug Multithreaded/ +Unicode Debug/ +Unicode Release Multithreaded DLL/ +Unicode Release Multithreaded Dll/ +Unicode Release Multithreaded/ +Unicode Release/ + +Win32/ +x64/ + + +# directories (by pattern) + +/build/ar* +/build/bc5* +/build/como* +/build/cw* +/build/gcc3* +/build/gcc4* +/build/icl* +/build/vc5* +/build/*.zip + +/include-*/ + + +# files (by name) + +.DS_Store +.ruby-version +.ruby-version-exclusions +.svnignore +.svnignore-at-root +.update_deps +.env.local +.env.development.local +.env.test.local +.env.production.local + +list_changes.cmd +logging-bailout.txt +prepare.cmd +sh.exe.stackdump +tidydir.cmd +update_deps.cmd + +build/makefile.tmpl +build/makefile.tools.xml +build/shwild.tmplgen +build/CMakeCache.txt + +include/fastformat/internal/dos2unix.has.been.performed +include/pantheios/internal/dos2unix.has.been.performed +include/stlsoft/dos2unix.has.been.performed +include/stlsoft/internal/dos2unix.has.been.performed + +test/testpatterns.orj +test/tests-plan.txt + + +# files (by pattern) + +*~ +*.*~ +*.???_obj +*.VC.db +*.VC.opendb +*.a +*.app +*.aps +*.b64 +*.bak +*.chm +*.class +*.d +*.diff +*.dll +*.dsp +*.dylib +*.exe +*.gch +*.gem +*.idb +*.ilk +*.iobj +*.ipch +*.ipdb +*.la +*.lai +*.lib +*.lo +*.log +*.ncb +*.o +*.obj +*.opensdf +*.opt +*.org +*.out +*.pch +*.pdb +*.pyc +*.rar +*.res +*.sbr +*.scc +*.sdf +*.slo +*.so +*.suo +*.swp +*.sw[ponm] +*.test +*.tlog +*.tmp +*.unsuccessfulbuild +*.vcproj*.user +*.vcxproj*.user +*.csproj*user +*.vcproj*user +*.vcxproj*user +*.xcuserstate +*.zip + +*.vc71.sln +*.vc8.sln +*.vc9.sln + +RCa0* +RCb0* + +build/*.tmplgen +build/*.tmpl +build/*.tools.xml + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + diff --git a/.sis/project_name.txt b/.sis/project_name.txt new file mode 100644 index 0000000..4a43d79 --- /dev/null +++ b/.sis/project_name.txt @@ -0,0 +1 @@ +Pantheios.Extras.AtExit diff --git a/.sis/script_info_lines.txt b/.sis/script_info_lines.txt new file mode 100644 index 0000000..b8bc637 --- /dev/null +++ b/.sis/script_info_lines.txt @@ -0,0 +1,3 @@ +Pantheios.Extras.AtExit - multi-callback atexit helper for C +Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems +Copyright (c) 2011-2019, Matthew Wilson and Synesis Software diff --git a/.vimrc b/.vimrc new file mode 100644 index 0000000..1bf33a0 --- /dev/null +++ b/.vimrc @@ -0,0 +1,70 @@ +" Synesis C/C++ project .vimrc — aligned with .sis/.vscode/c_cxx/settings.json + +set nocompatible +filetype indent plugin on +syntax enable +set autoindent +set backspace=indent,eol,start +set hlsearch +set incsearch +set number + +" files.insertFinalNewline +set eol +set fixeol + +" editor.renderWhitespace: all +set list +set listchars=tab:->,trail:-,extends:>,precedes:<,nbsp:+ + +" editor.detectIndentation: false — global defaults (editor.tabSize: 4, insertSpaces: true) +set colorcolumn=76 +set expandtab +set shiftwidth=4 +set softtabstop=4 +set tabstop=4 + +" colorcolumn draws a full-column tint in Vim (not a VS Code-style 1px line). +" Keep it subtle via the ColorColumn highlight group; reapply after colorscheme changes. +if has('termguicolors') + " set termguicolors +endif + +function! s:ConfigureColorColumn() abort + highlight ColorColumn ctermbg=236 guibg=#2a2a2a cterm=NONE gui=NONE +endfunction + +call s:ConfigureColorColumn() +autocmd ColorScheme * call s:ConfigureColorColumn() + +" files.trimTrailingWhitespace +autocmd BufWritePre * %s/\s\+$//e + +augroup sis_c_cxx + autocmd! + + " [c] / [cpp] + autocmd FileType c,cpp setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,64,68,72,76 + + " [rust] + autocmd FileType rs setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=76 + + " [cmake] + autocmd FileType cmake setlocal noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 + + " [shellscript] + autocmd FileType sh,bash,zsh setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 colorcolumn=60,76 + + " [bat] + autocmd FileType bat,dosbatch setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,76 + + " [json] / [markdown] / [yaml] / [ruby] + autocmd FileType json,markdown,yaml,ruby setlocal expandtab tabstop=2 shiftwidth=2 softtabstop=2 + + " [python] + autocmd FileType python setlocal expandtab tabstop=4 shiftwidth=4 softtabstop=4 colorcolumn=60,76 + + " [toml] + autocmd FileType toml setlocal noexpandtab tabstop=2 shiftwidth=2 softtabstop=2 +augroup END + diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3fe51d2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,182 @@ +{ + "[bat]": { + "editor.insertSpaces": true, + "editor.rulers": [ 60, 76 ], + "editor.tabSize": 4, + }, + "[c]": { + "editor.insertSpaces": true, + "editor.rulers": [ 60, 64, 68, 72, 76 ], + "editor.tabSize": 4, + }, + "[cmake]": { + "editor.insertSpaces": false, + "editor.tabSize": 4, + }, + "[cpp]": { + "editor.insertSpaces": true, + "editor.rulers": [ 60, 64, 68, 72, 76 ], + "editor.tabSize": 4, + }, + "[json]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + }, + "[markdown]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + }, + "[python]": { + "diffEditor.ignoreTrimWhitespace": false, + "editor.insertSpaces": true, + "editor.rulers": [ 51, 60, 61, 76 ], + "editor.tabSize": 4, + }, + "[ruby]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + }, + "[shellscript]": { + "editor.insertSpaces": true, + "editor.rulers": [ 60, 76 ], + "editor.tabSize": 2, + }, + "[toml]": { + "editor.insertSpaces": false, + "editor.tabSize": 2, + }, + "[yaml]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + }, + "cmake.configureOnOpen": false, + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.renderWhitespace": "all", + "editor.rulers": [ 76 ], + "editor.tabSize": 2, + "files.associations": { + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__functional_03": "cpp", + "__functional_base": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__memory": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "__verbose_abort": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "console_functions.h": "c", + "corecrt.h": "c", + "crtdefs.h": "c", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "execution": "cpp", + "fcntl.h": "c", + "format": "cpp", + "forward_list": "cpp", + "functional": "cpp", + "implicit_link.h": "c", + "initializer_list": "cpp", + "io.h": "c", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "queue": "cpp", + "random": "cpp", + "ranges": "cpp", + "ratio": "cpp", + "semaphore": "cpp", + "set": "cpp", + "setenv.h": "c", + "shwild.h": "c", + "span": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "stdio.h": "c", + "stop_token": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "terse-api.h": "c", + "text_encoding": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "uio.h": "c", + "unixem.h": "c", + "unordered_map": "cpp", + "util.h": "c", + "utility": "cpp", + "variant": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xtests.internal.string.c": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp", + }, + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, + "git.mergeEditor": false, +} diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..471321d --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,21 @@ +# Pantheios.Extras.AtExit - Authors + + +## Major Contributors + +| Name | GitHub | +| -------------- | ------------------------------------- | +| Matthew Wilson | [mwsis](https://github.com/mwsis) | + + +## Defect reports, fixes and suggestions (for which we are very grateful) + +| Name | GitHub | +| ------- | ------ | +| \ | | + + +Contributions are welcomed. + + + diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..f967f0b --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,32 @@ +# Pantheios.Extras.AtExit - Changes + + +## 0.1.3 - 31st August 2026 + +* Retargeted the current line from **0.1.2-alpha1** to **0.1.3**; +* Asserted **`pantheios_extras_atexit_init()`** `reserved0` / `reserved1` as `NULL` / `0`; +* Example **example.c.1** checks **`add`** return values; unit tests cover **`add`** without **`init`**, **`add`** after **`uninit`**, and version macros; +* Added a **README.md** rationale for why libc `atexit()` is insufficient and what this library is for; + + +## 0.1.2-alpha1 - 21st August 2026 + +* Modernised library version macros to computed `PANTHEIOS_EXTRAS_ATEXIT_VER` (`VER_MAJOR` / `VER_MINOR` / `VER_PATCH` / `VER_ALPHABETA`, with `VER_REVISION` alias) targeting **0.1.2-alpha1**; +* Added **ctest_cmake.sh** and **run_all_unit_tests.cmd**; +* Ensured C/C++ sources end with a blank line after the end-of-file marker, and used `char* argv[]` in the unit-test `main`; +* Renamed the scratch program implementation to **main.c**; +* Recorded functionality-review items in **TODO.md**; deferred applying them, and deferred the helper **MSYSTEM**/MinGW rewrite until the **HELPER-SCRIPTS-C-CXX.md** contract is frozen; + + +## 0.1.1 - 16th August 2026 + +* Recovered **Pantheios.Extras.AtExit** from Hautacam (`pantheios/extras/AtExit/0.1`, Created 30th December 2011) as a nested compiled C library; +* Added **CMake** packaging, **.sis** helper-script identity, modular GitHub Actions CI (**ci.yml** / **ci-cell.yml**), and install-smoke as a C consumer; +* Replaced the unshippable GCC atomic typedef with C11 `` `atomic_int` where available, else MSVC `InterlockedIncrement`/`Decrement`, else GCC/Clang `__sync_*`; +* Dropped the recovered `#error` that excluded Clang/AppleClang; +* Distinguished initialisation from an empty callback list via a dedicated `s_initialised` flag (first `add` is what sets `s_head`); +* Implemented `pantheios_extras_atexit_uninit()` to drain remaining callbacks (LIFO), free nodes, and leave the libc `atexit` hook as a no-op (libc still cannot unregister that hook, so a second `init` remains `EBUSY`); +* Added C example and **xTests** unit coverage for init / re-init / `add` / `uninit` drain; + + + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..44a1ca6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,279 @@ + +# ######################################################################## # +# File: /CMakeLists.txt +# +# Purpose: Top-level CMake lists file for Pantheios.Extras.AtExit +# +# Created: 16th August 2026 +# Updated: 16th August 2026 +# +# ######################################################################## # + + +# ########################################################## +# CMake + +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) + +# require out-of-source builds +file(TO_CMAKE_PATH "${CMAKE_CURRENT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) +if(EXISTS "${LOC_PATH}") + + message(FATAL_ERROR "You cannot build in a source directory (or any directory with a CMakeLists.txt file). Please make a build subdirectory. Feel free to remove CMakeCache.txt and CMakeFiles.") +endif() + +# directory for CMake specific extensions and source files. +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) + + +# ########################################################## +# project + +project(Pantheios.Extras.AtExit + DESCRIPTION "Pantheios.Extras.AtExit is a small C library that registers multiple atexit-style callbacks" + HOMEPAGE_URL "https://github.com/synesissoftware/Pantheios.Extras.AtExit" + LANGUAGES C CXX +) + +string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWER) +string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPER) + +# handle version number +set(RX_PROJ_TAG "PANTHEIOS_EXTRAS_ATEXIT") +set(RX_WS "[ \t]") +file(READ "${CMAKE_SOURCE_DIR}/include/pantheios/extras/atexit/atexit.h" _header_file) +string(REGEX MATCH "#${RX_WS}*define${RX_WS}+_?${RX_PROJ_TAG}_VER_MAJOR${RX_WS}+([0-9]+)" MAJOR_DUMMY ${_header_file}) +set(_VERSION_MAJOR ${CMAKE_MATCH_1}) +string(REGEX MATCH "#${RX_WS}*define${RX_WS}+_?${RX_PROJ_TAG}_VER_MINOR${RX_WS}+([0-9]+)" MINOR_DUMMY ${_header_file}) +set(_VERSION_MINOR ${CMAKE_MATCH_1}) +string(REGEX MATCH "#${RX_WS}*define${RX_WS}+_?${RX_PROJ_TAG}_VER_(PATCH|REVISION)${RX_WS}+([0-9]+)" PATCH_DUMMY ${_header_file}) +set(_VERSION_PATCH ${CMAKE_MATCH_2}) + +# set project version number here +set(PROJECT_VERSION_MAJOR ${_VERSION_MAJOR}) +set(PROJECT_VERSION_MINOR ${_VERSION_MINOR}) +set(PROJECT_VERSION_PATCH ${_VERSION_PATCH}) +set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + +# adhere strictly to C and C++ standards plus extensions. These state +# our intention for the core library (C) and xTests-backed unit tests (C++). +set(CMAKE_C_STANDARD 17) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) # GNU extensions and POSIX standard +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +if(MSVC) + + if(MSVC_VERSION LESS 1930) + + set(CMAKE_C_STANDARD 90) + set(CMAKE_CXX_STANDARD 98) + endif() + + if(MSVC_USE_MT) + + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif(MSVC_USE_MT) +else(MSVC) + + if(MSVC_USE_MT) + + # this here just to absorb warning about not using `MSVC_USE_MT` (to + # enable **prepare_cmake.sh** to be simple) + endif(MSVC_USE_MT) +endif(MSVC) + + +# ########################################################## +# dependencies, includes, options + +# ################################################ +# includes - 1 + +include(BuildType) +include(LanguageFullVersion) +include(TargetMacros) + + +# ################################################ +# dependencies, features, and options + +# ###################################### +# options + +option(BUILD_EXAMPLES "Build examples" ON) + +option(BUILD_TESTING "Build tests" ON) + + +# ###################################### +# dependencies +# +# required: +# - none for the core library; +# +# optional: +# - STLSoft - required by xTests (for testing); +# - xTests - required for testing; + + +# ############################ +# STLSoft + +if(BUILD_TESTING) + + if(NOT DEFINED STLSOFT AND NOT DEFINED ENV{STLSOFT}) + + set(STLSoft_REQUIRED_VERSION_ 1.11) + + find_package(STLSoft ${STLSoft_REQUIRED_VERSION_} REQUIRED) + + message("-- CMake package STLSoft found (version ${STLSoft_VERSION}; ${STLSoft_REQUIRED_VERSION_} requested)") + else() + + if(DEFINED STLSOFT) + + message("-- STLSOFT provided as CMake variable with value '${STLSOFT}'") + + set(STLSOFT_INCLUDE_DIR ${STLSOFT}/include) + else() + + message("-- STLSOFT provided as environment variable with value '$ENV{STLSOFT}'") + + set(STLSOFT_INCLUDE_DIR $ENV{STLSOFT}/include) + endif() + + get_filename_component(STLSOFT_INCLUDE_DIR "${STLSOFT_INCLUDE_DIR}" ABSOLUTE) + + include_directories(${STLSOFT_INCLUDE_DIR}) + endif() +endif() + + +# ############################ +# xTests + +if(BUILD_TESTING) + + set(xTests_REQUIRED_VERSION_ 0.26.3) + + find_package(xTests ${xTests_REQUIRED_VERSION_} REQUIRED) + + message("-- CMake package xTests found (version ${xTests_VERSION}; ${xTests_REQUIRED_VERSION_} requested)") +endif(BUILD_TESTING) + + +# ################################################ +# includes - 2 + +include(CMakePackageConfigHelpers) +if(BUILD_TESTING) + + include(CTest) +endif(BUILD_TESTING) +include(GNUInstallDirs) + + +# ########################################################## +# targets + +# ################################################ +# custom definitions + +if(UNIX) + + set(_BUILD_AS_UNIX 1) + + message("-- Operating system Unix") +else(UNIX) + + if(WIN32) + + set(_BUILD_AS_WIN32 1) + + message("-- Operating system Windows") + else(WIN32) + + message("-- Operating system unknown") + endif(WIN32) +endif(UNIX) + + +# ################################################ +# main libraries + +add_subdirectory(src) + + +# ################################################ +# examples + +if(BUILD_EXAMPLES) + + message("-- enabled building of examples ...") + + add_subdirectory(examples) +else(BUILD_EXAMPLES) + + message("-- disabled building of examples - define BUILD_EXAMPLES to enable") +endif(BUILD_EXAMPLES) + + +# ################################################ +# tests + +if(BUILD_TESTING) + + message("-- enabled building of tests ...") + + add_subdirectory(test) +else(BUILD_TESTING) + + message("-- disabled building of tests - define BUILD_TESTING to enable") +endif(BUILD_TESTING) + + +# ########################################################## +# export + +set(EXPORT_NAME ${PROJECT_NAME_LOWER}) + +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${EXPORT_NAME}-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cmake/${EXPORT_NAME}-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/${EXPORT_NAME}/cmake +) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/cmake/${EXPORT_NAME}-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion +) + +export(EXPORT project-targets + FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/${EXPORT_NAME}-targets.cmake" + NAMESPACE "${PROJECT_NAME}::" +) + +install(EXPORT project-targets + NAMESPACE "${PROJECT_NAME}::" + FILE "${EXPORT_NAME}-targets.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXPORT_NAME} +) + +install( FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${EXPORT_NAME}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${EXPORT_NAME}-config-version.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${EXPORT_NAME} +) + + +# ########################################################## +# completion + +message(NOTICE "Generating CMake build scripts for ${PROJECT_NAME} ${PROJECT_VERSION}, for C${CMAKE_C_STANDARD} C++${CMAKE_CXX_STANDARD}, with build type ${CMAKE_BUILD_TYPE}") + + +# ############################## end of file ############################# # diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..f2e52c7 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,63 @@ +# Pantheios.Extras.AtExit - FAQ + +The FAQ list is under (constant) development. If you post a question on the +Issues forum (https://github.com/synesissoftware/Pantheios.Extras.AtExit/issues) +it will be used to create one. + + +## Table of Contents + +- [Q1: "How do I build Pantheios.Extras.AtExit?"](#q1-how-do-i-build-pantheiosextrasatexit) +- [Q2: "How do I install Pantheios.Extras.AtExit?"](#q2-how-do-i-install-pantheiosextrasatexit) +- [Q3: "How do I use Pantheios.Extras.AtExit?"](#q3-how-do-i-use-pantheiosextrasatexit) +- [Q4: "Why does a second init fail after uninit?"](#q4-why-does-a-second-init-fail-after-uninit) + + +# FAQs: + +## Q1: "How do I build Pantheios.Extras.AtExit?" + +See [INSTALL.md](./INSTALL.md) for the recommended **CMake** flow +(**prepare_cmake.sh**, then **build_cmake.sh**). + +For a minimal core-only build with no external dependencies: + +```bash +$ ./prepare_cmake.sh --disable-testing --disable-examples -m +``` + +For a full build (including tests), install **STLSoft** 1.11 and **xTests** +first, then: + +```bash +$ ./prepare_cmake.sh -m +``` + +Execute `$ ./prepare_cmake.sh --help` for the full set of options. + + +## Q2: "How do I install Pantheios.Extras.AtExit?" + +See [INSTALL.md](./INSTALL.md). + + +## Q3: "How do I use Pantheios.Extras.AtExit?" + +Include **pantheios/extras/atexit.h** and link against +**libpantheios.extras.atexit** (the **CMake** target is +`Pantheios.Extras.AtExit::core`). + +Call `pantheios_extras_atexit_init(NULL, 0)` once, then +`pantheios_extras_atexit_add()` for each callback. Callbacks run in LIFO +order from `pantheios_extras_atexit_uninit()` or, if you do not uninit, from +the registered libc `atexit` hook. `pfn` must be non-`NULL` (`assert`). + + +## Q4: "Why does a second init fail after uninit?" + +`pantheios_extras_atexit_init()` registers one libc `atexit` handler. That +registration cannot be undone. `uninit()` drains and frees the callback +list so the hook is a no-op, but a second `init` still returns `EBUSY`. + + + diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..8fa09c5 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,154 @@ +# Pantheios.Extras.AtExit - Installation and Use + +**Pantheios.Extras.AtExit** is a classic-form C library, insofar as it has +implementation files in its **src** directory and header files in its +**include/pantheios/extras** directory. Thus, once "installed", one must +simply include **pantheios/extras/atexit.h** and compile-in or link-in the +implementation. + +The **C API** has no non-standard dependencies. Building the project's tests +additionally requires **STLSoft** and **xTests**. + + +## Table of Contents + +- [CMake](#cmake) +- [Bundled](#bundled) + + +## CMake + +The primary choice for installation is by use of **CMake**. + +1. Obtain the latest distribution of **Pantheios.Extras.AtExit**, from + https://github.com/synesissoftware/Pantheios.Extras.AtExit/, e.g. + + ```bash + $ mkdir -p ~/open-source + $ cd ~/open-source + $ git clone https://github.com/synesissoftware/Pantheios.Extras.AtExit/ + ``` + +2. Prepare the CMake configuration, via the **prepare_cmake.sh** script. + + For a minimal core-only install (no **STLSoft** / **xTests** required): + + ```bash + $ cd ~/open-source/Pantheios.Extras.AtExit + $ ./prepare_cmake.sh --disable-testing --disable-examples -v + ``` + + For a full build including examples and tests, install **STLSoft** 1.11 + (and **xTests** for tests) via their own **CMake** scripts first, then: + + ```bash + $ cd ~/open-source/Pantheios.Extras.AtExit + $ ./prepare_cmake.sh -v + ``` + + If **STLSoft** is available as a source tree rather than an installed + **CMake** package, pass its root with `--stlsoft-root-dir` / `-s`. + + (**Hint**: execute `$ ./prepare_cmake.sh --help` for more information.) + +3. Run a build of the generated **CMake**-derived build files via the + **build_cmake.sh** script, as in: + + ```bash + $ ./build_cmake.sh + ``` + + (**NOTE**: if you provide the flag `--run-make` (=== `-m`) in step 2 then + you do not need this step.) + +4. As a check (when testing was not disabled), execute the built unit-test + programs via **run_all_unit_tests.sh**, as in: + + ```bash + $ ./run_all_unit_tests.sh + ``` + +5. Install the library on the host, via `cmake`, as in: + + ```bash + $ sudo cmake --install ${SIS_CMAKE_BUILD_DIR:-./_build} --config Release + ``` + +6. Then to use the library, it is a simple matter as follows: + + 1. Assuming a simplest possible program to verify the installation: + + ```c + /* main.c */ + #include + + #include + #include + #include + + static void + on_exit_fn(void* param) + { + printf("callback %p\n", param); + } + + int main(void) + { + int const r = pantheios_extras_atexit_init(NULL, 0); + + if (0 != r) + { + fprintf(stderr, "%s\n", strerror(r)); + + return EXIT_FAILURE; + } + + pantheios_extras_atexit_add(on_exit_fn, (void*)1); + pantheios_extras_atexit_uninit(); + + return EXIT_SUCCESS; + } + ``` + + 2. Compile your project against **Pantheios.Extras.AtExit**: + + Due to the installation step (step 5 above) there is no requirement + for an explicit include directory: + + ```bash + $ cc -c main.c + ``` + + 3. Link your project against **Pantheios.Extras.AtExit**: + + ```bash + $ cc main.o -lpantheios.extras.atexit + ``` + + 4. Test your project: + + ```bash + $ ./a.out + callback 0x1 + $ + ``` + + Consumers that use **CMake** may instead depend on the installed package: + + ```cmake + find_package(Pantheios.Extras.AtExit REQUIRED) + target_link_libraries(your_target PRIVATE Pantheios.Extras.AtExit::core) + ``` + + +## Bundled + +**Pantheios.Extras.AtExit** is small enough that it may be bundled into +other projects. In that case: + +* add **Pantheios.Extras.AtExit**'s **include** directory to your project's include path; +* compile **src/atexit.api.c** into your build (or link a previously built **libpantheios.extras.atexit**); and +* `#include `. + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fce9e29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,33 @@ +Pantheios.Extras.AtExit - BSD 3-Clause License + +Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems +Copyright (c) 2011-2019, Matthew Wilson and Synesis Software + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..1311512 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,18 @@ +# Pantheios.Extras.AtExit - News + + +## Table of Contents + +- [News](#news) + + +## News + +| Date | News Item | +| ---------------- | -------------------------------------------------------------------------------- | +| 31st August 2026 | Pantheios.Extras.AtExit 0.1.3 | +| 21st August 2026 | Pantheios.Extras.AtExit 0.1.2-alpha1 | +| 16th August 2026 | Pantheios.Extras.AtExit 0.1.1 recovered and released | + + + diff --git a/README.md b/README.md index 73fa3ac..c5560e6 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,132 @@ Standalone C library that registers multiple `atexit`-style callbacks (function + `void*` parameter) under the **Pantheios.Extras** namespace. +![C](https://img.shields.io/badge/C-00599C?style=flat&logo=c&logoColor=white) +[![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) +[![GitHub release](https://img.shields.io/github/v/release/synesissoftware/Pantheios.Extras.AtExit.svg)](https://github.com/synesissoftware/Pantheios.Extras.AtExit/releases/latest) +[![Last Commit](https://img.shields.io/github/last-commit/synesissoftware/Pantheios.Extras.AtExit)](https://github.com/synesissoftware/Pantheios.Extras.AtExit/commits/master) +[![CI](https://github.com/synesissoftware/Pantheios.Extras.AtExit/actions/workflows/ci.yml/badge.svg)](https://github.com/synesissoftware/Pantheios.Extras.AtExit/actions/workflows/ci.yml) + + +## Table of Contents + +- [Introduction](#introduction) + - [Why at-exit functionality](#why-at-exit-functionality) + - [Dependencies](#dependencies) +- [Installation](#installation) +- [Components](#components) + - [C API / core library](#c-api--core-library) +- [Examples](#examples) +- [Project Information](#project-information) + - [Where to get help](#where-to-get-help) + - [Contribution guidelines](#contribution-guidelines) + - [Dependencies](#dependencies-1) + - [Development dependencies](#development-dependencies) + - [Related projects](#related-projects) + - [License](#license) + + +## Introduction + +**Pantheios.Extras.AtExit** is a small compiled **C** library in the [Pantheios](http://pantheios.org/) extras namespace. Unlike most/all of the other Pantheios Extras libraries, it is **not** a Pantheios (or STLSoft) dependency: the core target needs only the C standard library. + +Its raison d'être is to provide a richer alternative to the standard C library's `atexit()`, with an explicit callback list so client code can register many `(function, void* param)` pairs that are invoked in **LIFO** order — either when `pantheios_extras_atexit_uninit()` drains the list, or later via the single libc `atexit` hook registered at init. Importantly. each callback is also accompanied by a `void*` parameter that is given back to the callback when it is invoked, thereby enabling stateful cleanup. + +`pantheios_extras_atexit_init()` must be called at most once per process. Later calls fail (`EBUSY`) even after `uninit()`, because libc `atexit` handlers cannot be unregistered. After a drain, the registered hook is a no-op. + + +### Why at-exit functionality + +C programs often need last-chance cleanup: flushing diagnostics, releasing process-wide resources, or tearing down library state that has no natural owner once `main` has returned. libc `atexit()` is the portable hook for that, but it is a poor *unit of currency* for libraries and layered applications: + +* Handlers are `void (*)(void)`. Any context must live in globals, which couples unrelated components and makes reuse harder; +* The number of handlers is small and shared (`ATEXIT_MAX`, often 32). A library that registers one slot per subsystem, sink, or module can exhaust the table for the rest of the process; +* There is no unregister. A component that is done *before* process exit cannot drop its handler, and a second registration is another scarce slot; + +**Pantheios.Extras.AtExit** exists so that many callers can each register `(function, void* param)` without consuming a libc slot per callback. The library takes **one** `atexit` registration at `init` and maintains its own LIFO list. `uninit` drains that list early when the process is still in a well-defined state; if `uninit` is not used, the same list runs from the libc hook at exit. + +That is the same protocol as other **Pantheios.Extras** helpers: keep the core logging library free of this concern, and give C clients a small, stdlib-only facility instead of rolling an ad-hoc static list in every program. + + +### Dependencies + +| Component | Implemented in | Use in | Dependencies | +| ------------ | -------------- | ------------------------------------- | ------------ | +| Core library | C | C — via **pantheios/extras/atexit.h** | C standard library | +| Examples (C) | C | — | — | +| Tests (C) | C | — | [STLSoft](https://github.com/synesissoftware/STLSoft/), [xTests](https://github.com/synesissoftware/xTests/) | + + +## Installation + +Detailed instructions — via **CMake**, via bundling — are provided in the accompanying [INSTALL.md](./INSTALL.md) file. + + +## Components + +### C API / core library + +```C +int +pantheios_extras_atexit_init( + void* reserved0 +, unsigned reserved1 +); + +void +pantheios_extras_atexit_uninit( + void +); + +int +pantheios_extras_atexit_add( + void (*pfn)(void* param) +, void* param +); +``` + +`reserved0` / `reserved1` are unused and must be passed as `NULL` / `0`. + + +## Examples + +Examples are provided in the `examples` directory. + + +## Project Information + + +### Where to get help + +[GitHub Page](https://github.com/synesissoftware/Pantheios.Extras.AtExit) + + +### Contribution guidelines + +Defect reports, feature requests, and pull requests are welcome on https://github.com/synesissoftware/Pantheios.Extras.AtExit. + + +### Dependencies + +* none for the core library; + + +#### Development dependencies + +* [STLSoft](https://github.com/synesissoftware/STLSoft/); +* [xTests](https://github.com/synesissoftware/xTests/); + + +### Related projects + +* [Pantheios](https://github.com/synesissoftware/Pantheios); +* [Pantheios.Extras.DiagUtil](https://github.com/synesissoftware/Pantheios.Extras.DiagUtil); +* [Pantheios.Extras.Main](https://github.com/synesissoftware/Pantheios.Extras.Main); + + +### License + +**Pantheios.Extras.AtExit** is released under the 3-clause BSD license. See [LICENSE](./LICENSE) for details. + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..65503d5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,45 @@ +# Pantheios.Extras.AtExit - TODO + + +## Functional improvements + + +### High + +* [ ] Document (and optionally enforce) **`add` after `uninit`**: the libc `atexit` hook stays registered, so later **`add`** callbacks still run at process exit; docs currently imply the hook is a permanent no-op after drain; +* [ ] Document (or change) drain-while-locked: callbacks run under the spinlock, so a callback that calls **`add`** / **`uninit`** deadlocks; either forbid re-entrancy or copy the list, unlock, then invoke; + + +### Medium + +* [ ] Serialise **`init`**: `s_initialised` is a plain `int` and `init` is not under the lock, so two threads can register two `atexit` hooks; +* [x] ~~~Align **`reserved0`** / **`reserved1`**: header says they must be `NULL` / `0`, but the implementation ignores them — `assert` or drop the requirement;~~~ ✅ +* [ ] Stop treating **`atexit()`** failure as an `errno` / **`strerror()`** code; `EBUSY` / `ENOMEM` are errno values, `atexit` failure often is not; + + +### Low + +* [ ] Document **`add` without `init`**: the list grows, but process exit will not drain it unless **`uninit`** is called; +* [ ] Initialise C11 `atomic_int s_mx` with **`ATOMIC_VAR_INIT(0)`** if compilers warn on `= 0`; +* [x] ~~~Tests: **`add`** without **`init`** then **`uninit`**; **`add`** after **`uninit`**; version-macro unit test; keep process-exit coverage as scratch (or one automated case);~~~ ✅ +* [x] ~~~Example: check **`add`** return values;~~~ ✅ + + +### Enhancements + +* [ ] C++ RAII / `scope` wrapper; +* [ ] Unix-only pthread synchronisation scratch (with a Windows equivalent if revived); + + +## Performance improvements + +* \ + + +## Packaging improvements + +* [ ] Defer rewriting helper `MSYSTEM` → `mingw32-make` / MinGW-generator detection until the **HELPER-SCRIPTS-C-CXX.md** contract is frozen (same defect class as other Synesis C/C++ helpers); +* [ ] Split the unit-test implementation from its entry TU (then name the entry **entry.c**); until then keep the single-file **test.unit.atexit.api.c**; + + + diff --git a/build_cmake.sh b/build_cmake.sh new file mode 100755 index 0000000..1f6b55d --- /dev/null +++ b/build_cmake.sh @@ -0,0 +1,125 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make +MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} + +IgnoreRemainingFlagsAndOptions=0 +Targets=() + + +# ########################################################## +# functions + +function join_by { local IFS="$1"; shift; echo "$*"; } + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + if [ $IgnoreRemainingFlagsAndOptions -ne 0 ]; then + + Targets+=($1) + + shift + + continue + else + + if [ ! ${1:0:1} = '-' ]; then + + Targets+=($1) + + shift + + continue + fi + fi + + case $1 in + --) + + IgnoreRemainingFlagsAndOptions=1 + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Executes CMake-generated artefacts to (re)build project + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +if [ ! -d "$CMakeDir" ]; then + + >&2 echo "$ScriptPath: CMake build directory '$CMakeDir' not found so nothing to do; use script 'prepare_cmake.sh' if you wish to prepare CMake artefacts" + + exit 1 +else + + cd $CMakeDir + + if [ ! -f "$CMakeDir/Makefile" ]; then + + >&2 echo "$ScriptPath: CMake build directory '$CMakeDir' does not contain expected file 'Makefile', so a clean cannot be performed. It is recommended that you remove all CMake artefacts using script 'remove_cmake_artefacts.sh' followed by regeneration via 'prepare_cmake.sh'" + + cd ->/dev/null + + exit 1 + else + + if [ -z "$Targets" ]; then + + echo "Executing build (via command \`$MakeCmd\`)" + else + + echo "Executing build (via command \`$MakeCmd\`) with specific target(s) $(join_by , "${Targets[@]}")" + fi + + $MakeCmd ${Targets[*]} + status=$? + + cd ->/dev/null + + exit $status + fi +fi + + +# ############################## end of file ############################# # + diff --git a/clean_cmake.sh b/clean_cmake.sh new file mode 100755 index 0000000..87b42eb --- /dev/null +++ b/clean_cmake.sh @@ -0,0 +1,87 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make +MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Executes CMake-generated artefacts to clean project + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +if [ ! -d "$CMakeDir" ]; then + + >&2 echo "$ScriptPath: CMake build directory '$CMakeDir' not found so nothing to do; use script 'prepare_cmake.sh' if you wish to prepare CMake artefacts" + + exit 1 +else + + cd $CMakeDir + + if [ ! -f "$CMakeDir/Makefile" ]; then + + >&2 echo "$ScriptPath: CMake build directory '$CMakeDir' does not contain expected file 'Makefile', so a clean cannot be performed. It is recommended that you remove all CMake artefacts using script 'remove_cmake_artefacts.sh' followed by regeneration via 'prepare_cmake.sh'" + + cd ->/dev/null + + exit 1 + else + + echo "Cleaning build (via command \`$MakeCmd clean\`)" + + $MakeCmd clean + status=$? + + cd ->/dev/null + + exit $status + fi +fi + + +# ############################## end of file ############################# # + diff --git a/cmake/BuildType.cmake b/cmake/BuildType.cmake new file mode 100644 index 0000000..e83cfbb --- /dev/null +++ b/cmake/BuildType.cmake @@ -0,0 +1,61 @@ + +# ######################################################################## # +# File: /cmake/BuildType.cmake +# +# Purpose: CMake module file (for BuildType) +# +# Created: 16th October 2019 +# Updated: 12th January 2024 +# +# ######################################################################## # + + +# Including this module sets the `CMAKE_BUILD_TYPE` value as follows: +# +# 1. If user specifies on the command line, then `CMAKE_BUILD_TYPE` already +# has a valid, so this is accepted; +# 2. If the top-level .git directory is specified, then "Debug" is used; +# otherwise +# 3. "Release" is used. +# +# Note: +# - `CMAKE_BUILD_TYPE` is the build (configuration) type; +# - `CMAKE_CONFIGURATION_TYPES` is, if specified, a list of valid +# configurations (see +# https://cmake.org/cmake/help/latest/variable/CMAKE_CONFIGURATION_TYPES.html); +# +# Example usage: +# +#[========[ + +# CMakeLists.txt + +include(BuildType) + +]========] + + +if(EXISTS "${CMAKE_SOURCE_DIR}/.git") + + set(default_build_type "Debug") +else() + + set(default_build_type "Release") +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + + set(CACHE CMAKE_BUILD_TYPE "${default_build_type}" + STRING "Choose the type of build." FORCE + ) + + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo" + ) +endif() + +# ############################## end of file ############################# # + diff --git a/cmake/LanguageFullVersion.cmake b/cmake/LanguageFullVersion.cmake new file mode 100644 index 0000000..c0882b4 --- /dev/null +++ b/cmake/LanguageFullVersion.cmake @@ -0,0 +1,63 @@ + +# ################################################ +# X_CMAKE_C_FULLSTANDARD + +set(X_CMAKE_C_FULLSTANDARD 0) + +if(FALSE) +elseif(CMAKE_C_STANDARD EQUAL 99) + + set(X_CMAKE_C_FULLSTANDARD 1999) +elseif(CMAKE_C_STANDARD EQUAL 90) + + set(X_CMAKE_C_FULLSTANDARD 1990) +elseif(CMAKE_C_STANDARD GREATER_EQUAL 23) + + set(X_CMAKE_C_FULLSTANDARD 2023) +elseif(CMAKE_C_STANDARD GREATER_EQUAL 17) + + set(X_CMAKE_C_FULLSTANDARD 2017) +elseif(CMAKE_C_STANDARD GREATER_EQUAL 11) + + set(X_CMAKE_C_FULLSTANDARD 2011) +else() + + message(FATAL_ERROR "mechanism for discriminating X_CMAKE_C_FULLSTANDARD has failed") +endif() + + +# ################################################ +# X_CMAKE_CXX_FULLSTANDARD + +set(X_CMAKE_CXX_FULLSTANDARD 0) + +if(FALSE) +elseif(CMAKE_CXX_STANDARD EQUAL 98) + + set(X_CMAKE_CXX_FULLSTANDARD 1998) +elseif(CMAKE_CXX_STANDARD GREATER_EQUAL 26) + + set(X_CMAKE_CXX_FULLSTANDARD 2026) +elseif(CMAKE_CXX_STANDARD GREATER_EQUAL 23) + + set(X_CMAKE_CXX_FULLSTANDARD 2023) +elseif(CMAKE_CXX_STANDARD GREATER_EQUAL 20) + + set(X_CMAKE_CXX_FULLSTANDARD 2020) +elseif(CMAKE_CXX_STANDARD GREATER_EQUAL 17) + + set(X_CMAKE_CXX_FULLSTANDARD 2017) +elseif(CMAKE_CXX_STANDARD GREATER_EQUAL 14) + + set(X_CMAKE_CXX_FULLSTANDARD 2014) +elseif(CMAKE_CXX_STANDARD GREATER_EQUAL 11) + + set(X_CMAKE_CXX_FULLSTANDARD 2011) +else() + + message(FATAL_ERROR "mechanism for discriminating X_CMAKE_CXX_FULLSTANDARD has failed") +endif() + + +# ############################## end of file ############################# # + diff --git a/cmake/TargetMacros.cmake b/cmake/TargetMacros.cmake new file mode 100644 index 0000000..f66ca8b --- /dev/null +++ b/cmake/TargetMacros.cmake @@ -0,0 +1,104 @@ + +function(define_automated_test_program program_name entry_point_source_name) + + add_executable(${program_name} + ${entry_point_source_name} + ) + + target_link_libraries(${program_name} + PRIVATE + core + $<$:STLSoft::STLSoft> + $,xTests::xTests.core,xTests::core> + ) + + set_target_properties(${program_name} PROPERTIES + LINKER_LANGUAGE CXX + ) + + set(X_GCC_CUSTOM_WARNINGS_ "") + + if(X_GCC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED) + foreach(warning ${X_GCC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED}) + + list(APPEND X_GCC_CUSTOM_WARNINGS_ "-Wno-${warning}") + endforeach() + endif() + + set(X_MSVC_CUSTOM_WARNINGS_ "") + + if(X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED) + foreach(warning ${X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED}) + + list(APPEND X_MSVC_CUSTOM_WARNINGS_ "/wd${warning}") + endforeach() + endif() + + target_compile_options(${program_name} PRIVATE + $<$,$,$>: + -Werror -Wall -Wextra -pedantic + + ${GCC_WARN_NO_cxx11_long_long} + ${X_GCC_CUSTOM_WARNINGS_} + > + $<$,$>: + -Wno-unused-lambda-capture + > + $<$: + /WX /W4 + + ${X_MSVC_CUSTOM_WARNINGS_} + > + ) +endfunction(define_automated_test_program) + + +function(define_example_program program_name entry_point_source_name) + + add_executable(${program_name} + ${entry_point_source_name} + ) + + target_link_libraries(${program_name} + PRIVATE + core + ) + + set(X_GCC_CUSTOM_WARNINGS_ "") + + if(X_GCC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED) + foreach(warning ${X_GCC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED}) + + list(APPEND X_GCC_CUSTOM_WARNINGS_ "-Wno-${warning}") + endforeach() + endif() + + set(X_MSVC_CUSTOM_WARNINGS_ "") + + if(X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED) + foreach(warning ${X_MSVC_CUSTOM_WARNINGS_TO_BE_SUPPRESSED}) + + list(APPEND X_MSVC_CUSTOM_WARNINGS_ "/wd${warning}") + endforeach() + endif() + + target_compile_options(${program_name} PRIVATE + $<$,$,$>: + -Werror -Wall -Wextra -pedantic + + ${GCC_WARN_NO_cxx11_long_long} + ${X_GCC_CUSTOM_WARNINGS_} + > + $<$,$>: + -Wno-unused-lambda-capture + > + $<$: + /WX /W4 + + ${X_MSVC_CUSTOM_WARNINGS_} + > + ) +endfunction(define_example_program) + + +# ############################## end of file ############################# # diff --git a/cmake/pantheios.extras.atexit-config.cmake.in b/cmake/pantheios.extras.atexit-config.cmake.in new file mode 100644 index 0000000..479320d --- /dev/null +++ b/cmake/pantheios.extras.atexit-config.cmake.in @@ -0,0 +1,2 @@ +@PACKAGE_INIT@ +include("${CMAKE_CURRENT_LIST_DIR}/@EXPORT_NAME@-targets.cmake") diff --git a/ctest_cmake.sh b/ctest_cmake.sh new file mode 100755 index 0000000..24c2172 --- /dev/null +++ b/ctest_cmake.sh @@ -0,0 +1,108 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make +MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") + +CMakeVerbose= +RunMake=1 + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --no-make|-M) + + RunMake=0 + ;; + --verbose|-V) + + CMakeVerbose=--verbose + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Runs CMake's CTest test program(s) + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + -M + --no-make + does not execute CMake and make before running tests + + -V + --verbose + verbose test output + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +status=0 + +if [ $RunMake -ne 0 ]; then + + echo "Executing build for ${ProjectName} (via command \`$MakeCmd\`) and then running all component and unit test programs" + + mkdir -p $CMakeDir || exit 1 + + cd $CMakeDir + + $MakeCmd + status=$? + + cd ->/dev/null +else + + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + + >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + fi +fi + +if [ $status -eq 0 ]; then + + echo "Running CMake tests" + + ctest --test-dir $CMakeDir $CMakeVerbose + status=$? +fi + +exit $status + + +# ############################## end of file ############################# # diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..b7bb9f1 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,33 @@ + +include_directories(${CMAKE_SOURCE_DIR}/include) + +if(_BUILD_AS_UNIX) + + add_definitions(-DUNIX) + add_definitions(-Dunix) +endif(_BUILD_AS_UNIX) + +if(_BUILD_AS_WIN32) + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + + message (STATUS "Compiling for 64-bit Windows") + + add_definitions(-DWIN64) + else() + + message (STATUS "Compiling for 32-bit Windows") + + add_definitions(-DWIN32) + endif() +endif(_BUILD_AS_WIN32) + +if((MSVC) AND (MSVC_VERSION GREATER_EQUAL 1914)) + + add_compile_options("/Zc:__cplusplus") +endif() + +add_subdirectory(c) + + +# ############################## end of file ############################# # diff --git a/examples/c/CMakeLists.txt b/examples/c/CMakeLists.txt new file mode 100644 index 0000000..49cb8cd --- /dev/null +++ b/examples/c/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(example.c.1) + + +# ############################## end of file ############################# # diff --git a/examples/c/example.c.1/CMakeLists.txt b/examples/c/example.c.1/CMakeLists.txt new file mode 100644 index 0000000..d0668ad --- /dev/null +++ b/examples/c/example.c.1/CMakeLists.txt @@ -0,0 +1 @@ +define_example_program(example.c.1 example.c.1.c) diff --git a/examples/c/example.c.1/example.c.1.c b/examples/c/example.c.1/example.c.1.c new file mode 100644 index 0000000..78d173d --- /dev/null +++ b/examples/c/example.c.1/example.c.1.c @@ -0,0 +1,104 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: example.c.1.c + * + * Purpose: Example of Pantheios.Extras.AtExit: register several atexit + * callbacks (LIFO) and drain them via uninit(). + * + * Created: 30th December 2011 + * Updated: 31st August 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#include + +#include +#include +#include + + +static void +fn1(void* param) +{ + printf("fn1(%p)\n", param); +} + +static void +fn2(void* param) +{ + printf("fn2(%p)\n", param); +} + + +static int +add( + void (*pfn)(void* param) +, void* param +) +{ + int const r = pantheios_extras_atexit_add(pfn, param); + + if (0 != r) + { +#ifdef _MSC_VER + char error_message[256]; + + strerror_s(error_message, sizeof(error_message), r); +#else + char const* error_message = strerror(r); +#endif + + fprintf( + stderr + , "failed to add Pantheios.Extras.AtExit callback : %s (%d)\n" + , error_message + , r + ); + } + + return r; +} + + +int +main(void) +{ + int r = pantheios_extras_atexit_init(NULL, 0); + + if (0 != r) + { +#ifdef _MSC_VER + char error_message[256]; + + strerror_s(error_message, sizeof(error_message), r); +#else + char const* error_message = strerror(r); +#endif + + fprintf( + stderr + , "failed to initialise Pantheios.Extras.AtExit : %s (%d)\n" + , error_message + , r + ); + + return EXIT_FAILURE; + } + + /* LIFO: last add is invoked first by uninit() / atexit */ + if (0 != add(fn1, (void*)1) || + 0 != add(fn2, (void*)2) || + 0 != add(fn1, (void*)3) || + 0 != add(fn2, (void*)4)) + { + return EXIT_FAILURE; + } + + pantheios_extras_atexit_uninit(); + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/include/pantheios/extras/atexit.h b/include/pantheios/extras/atexit.h new file mode 100644 index 0000000..4f75829 --- /dev/null +++ b/include/pantheios/extras/atexit.h @@ -0,0 +1,79 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: pantheios/extras/atexit.h + * + * Purpose: Main C header for the Pantheios.Extras.AtExit library. + * + * Created: 30th December 2011 + * Updated: 21st August 2026 + * + * Home: http://www.pantheios.org/ + * + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2011-2019, Matthew Wilson and Synesis Software + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name(s) of Matthew Wilson and Synesis Software nor the + * names of any contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ////////////////////////////////////////////////////////////////////// */ + + +/** \file pantheios/extras/atexit.h + * [C only] Main C header for the + * \ref group__library__pantheios_extras_atexit "Pantheios.Extras.AtExit" library. + */ + +#ifndef PANTHEIOS_EXTRAS_ATEXIT_INCL_PANTHEIOS_EXTRAS_H_ATEXIT +#define PANTHEIOS_EXTRAS_ATEXIT_INCL_PANTHEIOS_EXTRAS_H_ATEXIT + +/* ///////////////////////////////////////////////////////////////////////// + * version information + */ + +#ifndef PANTHEIOS_DOCUMENTATION_SKIP_SECTION +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_MAJOR 1 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_MINOR 0 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_REVISION 4 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_H_ATEXIT_EDIT 5 +#endif /* !PANTHEIOS_DOCUMENTATION_SKIP_SECTION */ + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +/* ///////////////////////////////////////////////////////////////////////// + * inclusion + */ + +#ifdef STLSOFT_CF_PRAGMA_ONCE_SUPPORT +# pragma once +#endif /* STLSOFT_CF_PRAGMA_ONCE_SUPPORT */ + +#endif /* !PANTHEIOS_EXTRAS_ATEXIT_INCL_PANTHEIOS_EXTRAS_H_ATEXIT */ + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/include/pantheios/extras/atexit/atexit.h b/include/pantheios/extras/atexit/atexit.h new file mode 100644 index 0000000..ce115ce --- /dev/null +++ b/include/pantheios/extras/atexit/atexit.h @@ -0,0 +1,192 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: pantheios/extras/atexit/atexit.h + * + * Purpose: Header file for Pantheios.Extras.AtExit. + * + * Created: 30th December 2011 + * Updated: 31st August 2026 + * + * Home: http://www.pantheios.org/ + * + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2011-2019, Matthew Wilson and Synesis Software + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name(s) of Matthew Wilson and Synesis Software nor the + * names of any contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ////////////////////////////////////////////////////////////////////// */ + + +/** \file pantheios/extras/atexit/atexit.h + * [C only] API for the + * \ref group__library__pantheios_extras_atexit "Pantheios.Extras.AtExit" library. + */ + +#ifndef PANTHEIOS_EXTRAS_ATEXIT_INCL_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT +#define PANTHEIOS_EXTRAS_ATEXIT_INCL_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT + +/* ///////////////////////////////////////////////////////////////////////// + * version information + */ + +#ifndef PANTHEIOS_DOCUMENTATION_SKIP_SECTION +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_MAJOR 1 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_MINOR 2 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_REVISION 2 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT_EDIT 8 +#endif /* !PANTHEIOS_DOCUMENTATION_SKIP_SECTION */ + +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR + * The major version number of Pantheios.Extras.AtExit + */ + +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR + * The minor version number of Pantheios.Extras.AtExit + */ + +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH + * The patch version number of Pantheios.Extras.AtExit + */ + +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA + * The alpha/beta designator of Pantheios.Extras.AtExit, or 0xFF for a + * non-prerelease + */ + +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER + * The current composite version number of Pantheios.Extras.AtExit + */ + +/** \def PANTHEIOS_EXTRAS_ATEXIT_VER_REVISION + * Alias of PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH (for CMake scrapers / older + * consumers) + */ + +#ifndef PANTHEIOS_DOCUMENTATION_SKIP_SECTION +# define PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_1 0x000101ff +# define PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_2_ALPHA_1 0x00010241 +# define PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_3 0x000103ff +#endif /* !PANTHEIOS_DOCUMENTATION_SKIP_SECTION */ + +#define PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR 0 +#define PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR 1 +#define PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH 3 +#define PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA 0xFF + +#define PANTHEIOS_EXTRAS_ATEXIT_VER \ + (0\ + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR << 24 ) \ + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR << 16 ) \ + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH << 8 ) \ + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA << 0 ) \ + ) + +#define PANTHEIOS_EXTRAS_ATEXIT_VER_REVISION PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* ///////////////////////////////////////////////////////////////////////// + * API + */ + +/** Initialises the Pantheios.Extras.AtExit library, registering the API + * state with the C standard library function atexit(). + * + * Must not be called more than once per process. Subsequent calls fail + * even after \c pantheios_extras_atexit_uninit(), because libc + * atexit() handlers cannot be unregistered. + * + * \param reserved0 Currently unused. Must be NULL. + * \param reserved1 Currently unused. Must be 0. + * + * \retval 0 The library was initialised successfully. + * \retval !0 The library was not initialised successfully. The return value + * is a C failure code, and may be used with strerror() to + * obtain a human-readable string form of the failure. + * + * \warning Failure to call this function will mean that no callbacks + * registered by pantheios_extras_atexit_add() will be invoked at process + * exit (they may still be invoked by pantheios_extras_atexit_uninit()). + * + * \pre (NULL == reserved0) + * \pre (0 == reserved1) + */ +int +pantheios_extras_atexit_init( + void* reserved0 +, unsigned reserved1 +); + +/** Uninitialises the Pantheios.Extras.AtExit library. + * + * Invokes remaining registered callbacks (LIFO) and frees the list so the + * registered atexit() hook is a no-op. Does not (and cannot) + * unregister the libc atexit() handler. + */ +void +pantheios_extras_atexit_uninit( + void +); + +/** Adds a callback function to the Pantheios.Extras.AtExit library, which + * will be called, along with the given callback parameter, during + * atexit()-processing, or during + * \c pantheios_extras_atexit_uninit() if that is invoked first. + * + * Callbacks are invoked in LIFO order. + * + * \param pfn Callback function. + * \param param Parameter to be passed to the callback function when it is + * invoked. + * + * \retval 0 The callback function was successfully registered. + * \retval !0 The callback function was not registered successfully. The + * return value is a C failure code, and may be used with + * strerror() to obtain a human-readable string form of the + * failure. + * + * \pre (NULL != pfn) + */ +int +pantheios_extras_atexit_add( + void (*pfn)(void* param) +, void* param +); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#ifdef STLSOFT_CF_PRAGMA_ONCE_SUPPORT +# pragma once +#endif /* STLSOFT_CF_PRAGMA_ONCE_SUPPORT */ + +#endif /* !PANTHEIOS_EXTRAS_ATEXIT_INCL_PANTHEIOS_EXTRAS_ATEXIT_H_ATEXIT */ + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/prepare_cmake.sh b/prepare_cmake.sh new file mode 100755 index 0000000..cd236f2 --- /dev/null +++ b/prepare_cmake.sh @@ -0,0 +1,193 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make +MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} + +Configuration=Release +ExamplesDisabled=0 +MSVC_MT=0 +MinGW=0 +RunMake=0 +STLSoftDirGiven= +TestingDisabled=0 +VerboseMakefile=0 + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --cmake-verbose-makefile|-v) + + VerboseMakefile=1 + ;; + --debug-configuration|-d) + + Configuration=Debug + ;; + --disable-examples|-E) + + ExamplesDisabled=1 + ;; + --disable-testing|-T) + + TestingDisabled=1 + ;; + --mingw) + + MinGW=1 + ;; + --msvc-mt) + + MSVC_MT=1 + ;; + --run-make|-m) + + RunMake=1 + ;; + --stlsoft-root-dir|-s) + + shift + STLSoftDirGiven=$1 + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Creates/reinitialises the CMake build script(s) for ${ProjectName} + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + -v + --cmake-verbose-makefile + configures CMake to run verbosely (by setting CMAKE_VERBOSE_MAKEFILE + to be ON) + + -d + --debug-configuration + use Debug configuration (by setting CMAKE_BUILD_TYPE=Debug). Default + is to use Release + + -E + --disable-examples + disables building of examples (by setting BUILD_EXAMPLES=OFF) + + -T + --disable-testing + disables building of tests (by setting BUILD_TESTING=OFF). Unless + testing is disabled the STLSoft and xTests libraries will be + required to be available to CMake + + --mingw + uses explicitly the "MinGW Makefiles" generator + + --msvc-mt + when using Visual C++ (MSVC), the static runtime library will be + selected; the default is the dynamic runtime library + + -m + --run-make + executes make after a successful running of CMake + + -s + --stlsoft-root-dir + specifies the STLSoft root-directory, which will be passed to CMake + as the variable STLSOFT, and which will override the environment + variable STLSOFT (if present) + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +mkdir -p $CMakeDir || exit 1 + +cd $CMakeDir + +echo "Executing CMake (in ${CMakeDir})" + +if [ $ExamplesDisabled -eq 0 ]; then CMakeBuildExamplesFlag="ON" ; else CMakeBuildExamplesFlag="OFF" ; fi +if [ $MSVC_MT -eq 0 ]; then CMakeMsvcMtFlag="OFF" ; else CMakeMsvcMtFlag="ON" ; fi +if [ -z $STLSoftDirGiven ]; then CMakeSTLSoftVariable="" ; else CMakeSTLSoftVariable="-DSTLSOFT=$STLSoftDirGiven/" ; fi +if [ $TestingDisabled -eq 0 ]; then CMakeBuildTestingFlag="ON" ; else CMakeBuildTestingFlag="OFF" ; fi +if [ $VerboseMakefile -eq 0 ]; then CMakeVerboseMakefileFlag="OFF" ; else CMakeVerboseMakefileFlag="ON" ; fi + +if [ $MinGW -ne 0 ]; then + + cmake \ + $CMakeSTLSoftVariable \ + -DBUILD_EXAMPLES:BOOL=$CMakeBuildExamplesFlag \ + -DBUILD_TESTING:BOOL=$CMakeBuildTestingFlag \ + -DCMAKE_BUILD_TYPE=$Configuration \ + -G "MinGW Makefiles" \ + -S $Dir \ + -B $CMakeDir \ + || (cd ->/dev/null ; exit 1) +else + + cmake \ + $CMakeSTLSoftVariable \ + -DBUILD_EXAMPLES:BOOL=$CMakeBuildExamplesFlag \ + -DBUILD_TESTING:BOOL=$CMakeBuildTestingFlag \ + -DCMAKE_BUILD_TYPE=$Configuration \ + -DCMAKE_VERBOSE_MAKEFILE:BOOL=$CMakeVerboseMakefileFlag \ + -DMSVC_USE_MT:BOOL=$CMakeMsvcMtFlag \ + -S $Dir \ + -B $CMakeDir \ + || (cd ->/dev/null ; exit 1) +fi + +status=0 + +if [ $RunMake -ne 0 ]; then + + echo "Executing build (via command \`$MakeCmd\`)" + + $MakeCmd + status=$? +fi + +cd ->/dev/null + +if [ $VerboseMakefile -ne 0 ]; then + + echo -e "contents of $CMakeDir:" + ls -al $CMakeDir +fi + +exit $status + + +# ############################## end of file ############################# # diff --git a/remove_cmake_artefacts.sh b/remove_cmake_artefacts.sh new file mode 100755 index 0000000..076a84a --- /dev/null +++ b/remove_cmake_artefacts.sh @@ -0,0 +1,153 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} + +Directories=( + CMakeFiles + Testing + cmake + examples + projects + src + test +) +Files=( + CMakeCache.txt + CTestTestfile.cmake + DartConfiguration.tcl + Makefile + cmake_install.cmake + install_manifest.txt +) + + +# ########################################################## +# operating environment detection + +OsName="$(uname -s)" +case "${OsName}" in + CYGWIN*|MINGW*|MSYS_NT*) + + Directories+=( + ARM64 + Win32 + x64 + ) + Files+=( + "*.filters" + "*.sln" + "*.vcxproj" + ) + ;; + *) + + ;; +esac + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Removes all known CMake artefacts + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +if [ ! -d "$CMakeDir" ]; then + + echo "$ScriptPath: CMake build directory '$CMakeDir' not found so nothing to do; use script 'prepare_cmake.sh' if you wish to prepare CMake artefacts" + + exit 0 +else + + echo "Removing all cmake artefacts in '$CMakeDir'" + + num_dirs_removed=0 + num_files_removed=0 + + for d in ${Directories[@]} + do + + fq_dir_path="$CMakeDir/$d" + + [ -d "$fq_dir_path" ] || continue + + echo "removing directory '$d'" + + rm -dfr "$fq_dir_path" + + num_dirs_removed=$((num_dirs_removed+1)) + done + + cd "$CMakeDir" + + for f in ${Files[@]} + do + + for fq_file_path in $f + do + + [ -f "$fq_file_path" ] || continue + + echo "removing file '$fq_file_path'" + + rm -f "$fq_file_path" + + num_files_removed=$((num_files_removed+1)) + done + done + + cd ->/dev/null + + if [ 0 -eq $num_dirs_removed ] && [ 0 -eq $num_files_removed ]; then + + echo "nothing to do" + else + + echo "removed $num_dirs_removed directories and $num_files_removed files" + fi +fi + + +# ############################## end of file ############################# # + diff --git a/run_all_examples.sh b/run_all_examples.sh new file mode 100755 index 0000000..bb7ef70 --- /dev/null +++ b/run_all_examples.sh @@ -0,0 +1,132 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make +MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} + +ListOnly=0 +RunMake=1 + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --list-only|-l) + + ListOnly=1 + ;; + --no-make|-M) + + RunMake=0 + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Runs all example programs + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + -l + --list-only + lists the target programs but does not execute them + + -M + --no-make + does not execute CMake and make before running tests + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +status=0 + +if [ $RunMake -ne 0 ]; then + + if [ $ListOnly -eq 0 ]; then + + echo "Executing build (via command \`$MakeCmd\`) and then running all example programs" + + mkdir -p $CMakeDir || exit 1 + + cd $CMakeDir + + $MakeCmd + status=$? + + cd ->/dev/null + fi +else + + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + + >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + fi +fi + +if [ $status -eq 0 ]; then + + if [ $ListOnly -ne 0 ]; then + + echo "Listing all example programs" + else + + echo "Running all example programs" + fi + + for f in $(find $CMakeDir -type f '(' -name 'example.c.*' -o -name 'example.cpp.*' ')' -exec test -x {} \; -print) + do + + if [ $ListOnly -ne 0 ]; then + + echo "would execute $f:" + + continue + fi + + echo + echo "executing $f:" + + # NOTE: we do not break on fail, because, this being a unit-testing library, the scratch-tests actually fail + $f + done +fi + +exit $status + + +# ############################## end of file ############################# # + diff --git a/run_all_scratch_tests.sh b/run_all_scratch_tests.sh new file mode 100755 index 0000000..25aa4c1 --- /dev/null +++ b/run_all_scratch_tests.sh @@ -0,0 +1,137 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make +MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} + +ListOnly=0 +RunMake=1 + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --list-only|-l) + + ListOnly=1 + ;; + --no-make|-M) + + RunMake=0 + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Runs all (matching) scratch-test programs + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + -l + --list-only + lists the target programs but does not execute them + + -M + --no-make + does not execute CMake and make before running tests + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + + +# ########################################################## +# main() + +status=0 + +if [ $RunMake -ne 0 ]; then + + if [ $ListOnly -eq 0 ]; then + + echo "Executing build (via command \`$MakeCmd\`) and then running all scratch test programs" + + mkdir -p $CMakeDir || exit 1 + + cd $CMakeDir + + $MakeCmd + status=$? + + cd ->/dev/null + fi +else + + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + + >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + fi +fi + +if [ $status -eq 0 ]; then + + if [ $ListOnly -ne 0 ]; then + + echo "Listing all scratch (and performance) test programs" + else + + echo "Running all scratch (and performance) test programs" + fi + + for f in $(find $CMakeDir -type f '(' -name 'test_scratch*' -o -name 'test.scratch.*' -o -name 'test_performance*' -o -name 'test.performance.*' ')' -exec test -x {} \; -print) + do + + if [ $ListOnly -ne 0 ]; then + + echo "would execute $f:" + + continue + fi + + echo + echo "executing $f:" + + if $f; then + + : + else + + status=$? + fi + done +fi + +exit $status + + +# ############################## end of file ############################# # + diff --git a/run_all_unit_tests.cmd b/run_all_unit_tests.cmd new file mode 100644 index 0000000..b68c97e --- /dev/null +++ b/run_all_unit_tests.cmd @@ -0,0 +1,188 @@ +@echo off + +SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION + +SET SCRIPT_DIRECTORY=%~dp0 +SET SCRIPT_PATH_DOC=%~n0[%~x0] +IF DEFINED SIS_CMAKE_BUILD_DIR ( + + SET "CMAKE_DIR=%SIS_CMAKE_BUILD_DIR%" +) ELSE ( + + SET "CMAKE_DIR=%SCRIPT_DIRECTORY%_build" +) + +SET ListOnly=0 +SET Verbose=0 +SET UnitOnly=0 +SET ComponentOnly=0 +SET status=0 + +FOR %%a IN (%*) DO ( + IF /I {--help}=={%%a} ( + IF EXIST "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" ( + + type "%SCRIPT_DIRECTORY%.sis\script_info_lines.txt" + ) + ECHO ^ + +Runs all ^(matching^) unit-test and/or component-test programs ^ + +^ + +%SCRIPT_PATH_DOC% [ ... flags/options ... ] ^ + +^ + +Flags/options: ^ + + behaviour: ^ + +^ + + -l ^ + + --list-only ^ + + lists the target programs but does not execute them ^ + +^ + + -M ^ + + --no-make ^ + + accepted for parity with the .sh script ^(build is not invoked^) ^ + +^ + + --unit-only ^ + + runs only unit-test programs ^(test.unit.* / test_unit*^) ^ + +^ + + --component-only ^ + + runs only component-test programs ^(test.component.* / test_component*^) ^ + +^ + + -v ^ + + --verbose ^ + + lists each test program before executing it ^ + +^ + + standard flags: ^ + +^ + + --help ^ + + displays this help and terminates ^ + + + EXIT /B 0 + ) ELSE IF /I {-l}=={%%a} ( + SET ListOnly=1 + ) ELSE IF /I {--list-only}=={%%a} ( + SET ListOnly=1 + ) ELSE IF /I {-M}=={%%a} ( + REM no-op: this .cmd never invokes the build + ) ELSE IF /I {--no-make}=={%%a} ( + REM no-op: this .cmd never invokes the build + ) ELSE IF /I {--unit-only}=={%%a} ( + SET UnitOnly=1 + ) ELSE IF /I {--component-only}=={%%a} ( + SET ComponentOnly=1 + ) ELSE IF /I {-v}=={%%a} ( + SET Verbose=1 + ) ELSE IF /I {--verbose}=={%%a} ( + SET Verbose=1 + ) ELSE ( + ECHO %SCRIPT_PATH_DOC%: unrecognised argument '%%a'; use --help for usage 1>&2 + + EXIT /B 1 + ) +) + +IF !UnitOnly! EQU 1 IF !ComponentOnly! EQU 1 ( + + ECHO %SCRIPT_PATH_DOC%: --unit-only and --component-only are mutually exclusive 1>&2 + + EXIT /B 1 +) + +IF NOT EXIST "%CMAKE_DIR%" ( + + ECHO %SCRIPT_PATH_DOC%: CMake build directory '%CMAKE_DIR%' does not exist 1>&2 + + EXIT /B 1 +) + +SET "ProjectName=" +FOR /F "usebackq delims=" %%p IN ("%SCRIPT_DIRECTORY%.sis\project_name.txt") DO SET "ProjectName=%%p" + +IF NOT DEFINED ProjectName ( + + ECHO %SCRIPT_PATH_DOC%: could not read project name from .sis\project_name.txt 1>&2 + + EXIT /B 1 +) + +IF !UnitOnly! EQU 1 ( + + SET "TestKindDescription=unit test" +) ELSE IF !ComponentOnly! EQU 1 ( + + SET "TestKindDescription=component test" +) ELSE ( + + SET "TestKindDescription=component and unit test" +) + +IF !ListOnly! EQU 1 ( + + ECHO Listing all !ProjectName! !TestKindDescription! programs +) ELSE ( + + ECHO Running all !ProjectName! !TestKindDescription! programs +) + +IF !UnitOnly! EQU 1 ( + + SET "FindPattern=test\.unit\..*\.exe$" +) ELSE IF !ComponentOnly! EQU 1 ( + + SET "FindPattern=test\.component\..*\.exe$" +) ELSE ( + + SET "FindPattern=test\.unit\..*\.exe$ test\.component\..*\.exe$" +) + +FOR /F "usebackq delims=" %%f IN (`DIR /A:-D /B /S "%CMAKE_DIR%\*.exe" 2^>NUL ^| FINDSTR /I /R "!FindPattern!"`) DO ( + IF !ListOnly! EQU 1 ( + + ECHO would execute %%f: + ) ELSE ( + + IF !Verbose! EQU 1 ( + + ECHO executing %%f: + ) + + "%%f" + IF ERRORLEVEL 1 ( + + SET status=1 + + GOTO :done + ) + ) +) + +:done +EXIT /B !status! diff --git a/run_all_unit_tests.sh b/run_all_unit_tests.sh new file mode 100755 index 0000000..22efd84 --- /dev/null +++ b/run_all_unit_tests.sh @@ -0,0 +1,200 @@ +#! /bin/bash + +ScriptPath=$0 +Dir=$(cd $(dirname "$ScriptPath"); pwd) +ProjectNameFile="$Dir/.sis/project_name.txt" +ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") +Basename=$(basename "$ScriptPath") +CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} +[[ -n "$MSYSTEM" ]] && DefaultMakeCmd=mingw32-make.exe || DefaultMakeCmd=make +MakeCmd=${SIS_CMAKE_MAKE_COMMAND:-${SIS_CMAKE_COMMAND:-$DefaultMakeCmd}} + +ListOnly=0 +RunMake=1 +UnitOnly=0 +ComponentOnly=0 +Verbosity=${XTESTS_VERBOSITY:-${TEST_VERBOSITY:-3}} + + +# ########################################################## +# command-line handling + +while [[ $# -gt 0 ]]; do + + case $1 in + --list-only|-l) + + ListOnly=1 + ;; + --no-make|-M) + + RunMake=0 + ;; + --unit-only) + + UnitOnly=1 + ;; + --component-only) + + ComponentOnly=1 + ;; + --verbosity) + + shift + Verbosity=$1 + ;; + --help) + + [ -f "$Dir/.sis/script_info_lines.txt" ] && cat "$Dir/.sis/script_info_lines.txt" + cat << EOF +Runs all (matching) unit-test and/or component-test programs + +$ScriptPath [ ... flags/options ... ] + +Flags/options: + + behaviour: + + -l + --list-only + lists the target programs but does not execute them + + -M + --no-make + does not execute CMake and make before running tests + + --unit-only + runs only unit-test programs (test.unit.* / test_unit*) + + --component-only + runs only component-test programs (test.component.* / test_component*) + + --verbosity + specifies an explicit verbosity for the unit-test(s) + + + standard flags: + + --help + displays this help and terminates + +EOF + + exit 0 + ;; + *) + + >&2 echo "$ScriptPath: unrecognised argument '$1'; use --help for usage" + + exit 1 + ;; + esac + + shift +done + +if [ $UnitOnly -ne 0 ] && [ $ComponentOnly -ne 0 ]; then + + >&2 echo "$ScriptPath: --unit-only and --component-only are mutually exclusive" + + exit 1 +fi + + +# ########################################################## +# main() + +status=0 + +if [ $UnitOnly -ne 0 ]; then + + TestKindDescription='unit test' +elif [ $ComponentOnly -ne 0 ]; then + + TestKindDescription='component test' +else + + TestKindDescription='component and unit test' +fi + +if [ $RunMake -ne 0 ]; then + + if [ $ListOnly -eq 0 ]; then + + echo "Executing build (via command \`$MakeCmd\`) and then running all ${TestKindDescription} programs" + + mkdir -p $CMakeDir || exit 1 + + cd $CMakeDir + + $MakeCmd + status=$? + + cd ->/dev/null + fi +else + + if [ ! -d "$CMakeDir" ] || [ ! -f "$CMakeDir/CMakeCache.txt" ] || [ ! -d "$CMakeDir/CMakeFiles" ]; then + + >&2 echo "$ScriptPath: cannot run in '--no-make' mode without a previous successful build step" + + exit 1 + fi +fi + +if [ $status -eq 0 ]; then + + if [ $ListOnly -ne 0 ]; then + + echo "Listing all ${TestKindDescription} programs" + else + + echo "Running all ${TestKindDescription} programs" + fi + + if [ $UnitOnly -ne 0 ]; then + + find_name_expr=( \( -name 'test_unit*' -o -name 'test.unit.*' \) ) + elif [ $ComponentOnly -ne 0 ]; then + + find_name_expr=( \( -name 'test_component*' -o -name 'test.component.*' \) ) + else + + find_name_expr=( \( -name 'test_unit*' -o -name 'test.unit.*' -o -name 'test_component*' -o -name 'test.component.*' \) ) + fi + + for f in $(find "$CMakeDir" -type f "${find_name_expr[@]}" -exec test -x {} \; -print | sort) + do + + if [ $ListOnly -ne 0 ]; then + + echo "would execute $f:" + + continue + fi + + if [ $Verbosity -ge 3 ]; then + + echo + fi + if [ $Verbosity -ge 2 ]; then + + echo "executing $f:" + fi + + if $f --verbosity=$Verbosity; then + + : + else + + status=$? + + break 1 + fi + done +fi + +exit $status + + +# ############################## end of file ############################# # diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..32684b8 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,89 @@ + +# ######################################################################## # +# File: /src/CMakeLists.txt +# +# Purpose: CMake lists file for Pantheios.Extras.AtExit core library +# +# Created: 16th August 2026 +# Updated: 16th August 2026 +# +# ######################################################################## # + + +if(_BUILD_AS_UNIX) + + add_definitions(-DUNIX) + add_definitions(-Dunix) +endif(_BUILD_AS_UNIX) + +if(_BUILD_AS_WIN32) + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + + message (STATUS "Compiling for 64-bit Windows") + + add_definitions(-DWIN64) + else() + + message (STATUS "Compiling for 32-bit Windows") + + add_definitions(-DWIN32) + endif() +endif(_BUILD_AS_WIN32) + +if(MSVC) + if(MSVC_VERSION GREATER_EQUAL 1914) + + add_compile_options("/Zc:__cplusplus") + endif() +endif(MSVC) + + +set(CORE_SRCS + atexit.api.c +) + +add_library(core + ${CORE_SRCS} +) + +set_target_properties(core PROPERTIES + OUTPUT_NAME "pantheios.extras.atexit" +) + +target_include_directories(core + PUBLIC + $ + $ +) + +target_compile_options(core + PRIVATE + $<$,$,$>: + -Werror -Wall -Wextra -pedantic + > + $<$: + /WX /W4 + > +) + + +# ########################################################## +# install + +install(TARGETS core + EXPORT project-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install( + DIRECTORY ${CMAKE_SOURCE_DIR}/include/pantheios/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/pantheios/ + FILES_MATCHING + PATTERN "*.h*" +) + + +# ############################## end of file ############################# # diff --git a/src/atexit.api.c b/src/atexit.api.c new file mode 100644 index 0000000..6c7bf3a --- /dev/null +++ b/src/atexit.api.c @@ -0,0 +1,265 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: atexit.api.c + * + * Purpose: Implementation file for Pantheios.Extras.AtExit + * + * Created: 30th December 2011 + * Updated: 31st August 2026 + * + * Home: http://www.pantheios.org/ + * + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2011-2019, Matthew Wilson and Synesis Software + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name(s) of Matthew Wilson and Synesis Software nor the + * names of any contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * atomics + */ + +#if 0 +#elif 1 &&\ + defined(__STDC_VERSION__) &&\ + (__STDC_VERSION__ >= 201112L) &&\ + !defined(_MSC_VER) &&\ + 1 + +# include +typedef atomic_int pantheios_extras_atexit_atomic_int_t; +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) (atomic_fetch_add((p), 1) + 1) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) ((void)atomic_fetch_sub((p), 1)) +#elif defined(_WIN32) + +typedef LONG pantheios_extras_atexit_atomic_int_t; +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) InterlockedIncrement((p)) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) InterlockedDecrement((p)) +#elif defined(__GNUC__) || defined(__clang__) + +typedef int pantheios_extras_atexit_atomic_int_t; +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(p) __sync_add_and_fetch((p), 1) +# define PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(p) __sync_sub_and_fetch((p), 1) +#else + +# error Pantheios.Extras.AtExit requires C11 atomics, GCC/Clang __sync, or Windows Interlocked +#endif + + +/* ///////////////////////////////////////////////////////////////////////// + * types + */ + +struct pantheios_extras_atexit_context_t_; +typedef struct pantheios_extras_atexit_context_t_ pantheios_extras_atexit_context_t_; +struct pantheios_extras_atexit_context_t_ +{ + void (*pfn)(void* param); + void* param; + pantheios_extras_atexit_context_t_* next; +}; + + +/* ///////////////////////////////////////////////////////////////////////// + * globals + */ + +static pantheios_extras_atexit_context_t_* s_head = NULL; +static pantheios_extras_atexit_atomic_int_t s_mx = 0; +static int s_initialised = 0; + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +static +void +pantheios_extras_atexit_log_string_( + char const* message +); + +static +void +pantheios_extras_atexit_lock_(void) +{ + for (; 1 != PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_INC_(&s_mx); PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(&s_mx)) + {} +} + +static +void +pantheios_extras_atexit_unlock_(void) +{ + PANTHEIOS_EXTRAS_ATEXIT_ATOMIC_DEC_(&s_mx); +} + +static +void +pantheios_extras_atexit_drain_unlocked_(void) +{ + pantheios_extras_atexit_context_t_* item; + + for (item = s_head; NULL != item; ) + { + pantheios_extras_atexit_context_t_* next = item->next; + + item->pfn(item->param); + free(item); + + item = next; + } + + s_head = NULL; +} + +static +void +pantheios_extras_atexit_procedure_( + void +) +{ + pantheios_extras_atexit_lock_(); + pantheios_extras_atexit_drain_unlocked_(); + pantheios_extras_atexit_unlock_(); +} + + +/* ///////////////////////////////////////////////////////////////////////// + * API + */ + +int +pantheios_extras_atexit_init( + void* reserved0 +, unsigned reserved1 +) +{ + int r; + + assert(NULL == reserved0); + assert(0 == reserved1); + + ((void)reserved0); + ((void)reserved1); + + if (0 != s_initialised) + { + pantheios_extras_atexit_log_string_("Pantheios.Extras.AtExit already initialised!\n"); + + return EBUSY; + } + + r = atexit(pantheios_extras_atexit_procedure_); + + if (0 != r) + { + return r; + } + + s_initialised = 1; + + return 0; +} + +void +pantheios_extras_atexit_uninit( + void +) +{ + pantheios_extras_atexit_lock_(); + pantheios_extras_atexit_drain_unlocked_(); + pantheios_extras_atexit_unlock_(); +} + +int +pantheios_extras_atexit_add( + void (*pfn)(void* param) +, void* param +) +{ + pantheios_extras_atexit_context_t_* item; + + assert(NULL != pfn); + + item = (pantheios_extras_atexit_context_t_*)malloc(sizeof(pantheios_extras_atexit_context_t_)); + + if (NULL == item) + { + return ENOMEM; + } + + item->pfn = pfn; + item->param = param; + + pantheios_extras_atexit_lock_(); + + item->next = s_head; + s_head = item; + + pantheios_extras_atexit_unlock_(); + + return 0; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * helper definitions + */ + +static +void +pantheios_extras_atexit_log_string_( + char const* message +) +{ +#ifdef _WIN32 + OutputDebugStringA(message); +#endif /* _WIN32 */ + + fputs(message, stderr); +} + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..88529c5 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,34 @@ + +include_directories(${CMAKE_SOURCE_DIR}/include) + +if(_BUILD_AS_UNIX) + + add_definitions(-DUNIX) + add_definitions(-Dunix) +endif(_BUILD_AS_UNIX) + +if(_BUILD_AS_WIN32) + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + + message (STATUS "Compiling for 64-bit Windows") + + add_definitions(-DWIN64) + else() + + message (STATUS "Compiling for 32-bit Windows") + + add_definitions(-DWIN32) + endif() +endif(_BUILD_AS_WIN32) + +if((MSVC) AND (MSVC_VERSION GREATER_EQUAL 1914)) + + add_compile_options("/Zc:__cplusplus") +endif() + +add_subdirectory(unit) +add_subdirectory(scratch) + + +# ############################## end of file ############################# # diff --git a/test/scratch/CMakeLists.txt b/test/scratch/CMakeLists.txt new file mode 100644 index 0000000..c0cad39 --- /dev/null +++ b/test/scratch/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(test.scratch.atexit) + + +# ############################## end of file ############################# # diff --git a/test/scratch/test.scratch.atexit/CMakeLists.txt b/test/scratch/test.scratch.atexit/CMakeLists.txt new file mode 100644 index 0000000..67169b4 --- /dev/null +++ b/test/scratch/test.scratch.atexit/CMakeLists.txt @@ -0,0 +1 @@ +define_example_program(test.scratch.atexit main.c) diff --git a/test/scratch/test.scratch.atexit/main.c b/test/scratch/test.scratch.atexit/main.c new file mode 100644 index 0000000..e81da3b --- /dev/null +++ b/test/scratch/test.scratch.atexit/main.c @@ -0,0 +1,58 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: main.c + * + * Purpose: Scratch test for Pantheios.Extras.AtExit. + * + * Created: 30th December 2011 + * Updated: 21st August 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#include + +#include +#include +#include + + +static void +fn(void* param) +{ + printf("scratch callback %p\n", param); +} + + +int +main(void) +{ + int r = pantheios_extras_atexit_init(NULL, 0); + + if (0 != r) + { +#ifdef _MSC_VER + char error_message[256]; + + strerror_s(error_message, sizeof(error_message), r); +#else + char const* error_message = strerror(r); +#endif + + fprintf( + stderr + , "failed to initialise Pantheios.Extras.AtExit : %s (%d)\n" + , error_message + , r + ); + + return EXIT_FAILURE; + } + + pantheios_extras_atexit_add(fn, (void*)1); + pantheios_extras_atexit_add(fn, (void*)2); + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt new file mode 100644 index 0000000..cbcd22d --- /dev/null +++ b/test/unit/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(test.unit.atexit.api) +add_subdirectory(test.unit.version) + + +# ############################## end of file ############################# # diff --git a/test/unit/test.unit.atexit.api/CMakeLists.txt b/test/unit/test.unit.atexit.api/CMakeLists.txt new file mode 100644 index 0000000..47cc308 --- /dev/null +++ b/test/unit/test.unit.atexit.api/CMakeLists.txt @@ -0,0 +1 @@ +define_automated_test_program(test.unit.atexit.api test.unit.atexit.api.c) diff --git a/test/unit/test.unit.atexit.api/test.unit.atexit.api.c b/test/unit/test.unit.atexit.api/test.unit.atexit.api.c new file mode 100644 index 0000000..d0dc38f --- /dev/null +++ b/test/unit/test.unit.atexit.api/test.unit.atexit.api.c @@ -0,0 +1,163 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test.unit.atexit.api.c + * + * Purpose: Unit tests for Pantheios.Extras.AtExit. + * + * Created: 16th August 2026 + * Updated: 21st August 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#include + +#include + +#include +#include + + +static int s_calls[8]; +static int s_ncalls; +static int s_tag1 = 1; +static int s_tag2 = 2; +static int s_tag3 = 3; +static int s_tag7 = 7; +static int s_tag_pre = 11; +static int s_tag_post = 13; + + +static void +record(void* param) +{ + if (s_ncalls < (int)(sizeof(s_calls) / sizeof(s_calls[0]))) + { + s_calls[s_ncalls] = *(int*)param; + } + + ++s_ncalls; +} + + +static void +test_add_without_init_then_uninit(void) +{ + s_ncalls = 0; + + XTESTS_TEST_INTEGER_EQUAL(0, pantheios_extras_atexit_add(record, &s_tag_pre)); + + pantheios_extras_atexit_uninit(); + + XTESTS_TEST_INTEGER_EQUAL(1, s_ncalls); + XTESTS_TEST_INTEGER_EQUAL(11, s_calls[0]); +} + +static void +test_init_succeeds(void) +{ + int const r = pantheios_extras_atexit_init(NULL, 0); + + XTESTS_TEST_INTEGER_EQUAL(0, r); +} + +static void +test_second_init_fails(void) +{ + int const r = pantheios_extras_atexit_init(NULL, 0); + + XTESTS_TEST_INTEGER_NOT_EQUAL(0, r); + XTESTS_TEST_INTEGER_EQUAL(EBUSY, r); +} + +static void +test_uninit_invokes_lifo(void) +{ + s_ncalls = 0; + + XTESTS_TEST_INTEGER_EQUAL(0, pantheios_extras_atexit_add(record, &s_tag1)); + XTESTS_TEST_INTEGER_EQUAL(0, pantheios_extras_atexit_add(record, &s_tag2)); + XTESTS_TEST_INTEGER_EQUAL(0, pantheios_extras_atexit_add(record, &s_tag3)); + + pantheios_extras_atexit_uninit(); + + XTESTS_TEST_INTEGER_EQUAL(3, s_ncalls); + XTESTS_TEST_INTEGER_EQUAL(3, s_calls[0]); + XTESTS_TEST_INTEGER_EQUAL(2, s_calls[1]); + XTESTS_TEST_INTEGER_EQUAL(1, s_calls[2]); +} + +static void +test_uninit_is_idempotent_for_callbacks(void) +{ + int before; + + s_ncalls = 0; + + XTESTS_TEST_INTEGER_EQUAL(0, pantheios_extras_atexit_add(record, &s_tag7)); + + pantheios_extras_atexit_uninit(); + before = s_ncalls; + XTESTS_TEST_INTEGER_EQUAL(1, before); + + pantheios_extras_atexit_uninit(); + + XTESTS_TEST_INTEGER_EQUAL(before, s_ncalls); +} + + +static void +test_init_after_uninit_fails(void) +{ + int const r = pantheios_extras_atexit_init(NULL, 0); + + XTESTS_TEST_INTEGER_EQUAL(EBUSY, r); +} + +static void +test_add_after_uninit_then_uninit_again(void) +{ + s_ncalls = 0; + + XTESTS_TEST_INTEGER_EQUAL(0, pantheios_extras_atexit_add(record, &s_tag_post)); + + pantheios_extras_atexit_uninit(); + + XTESTS_TEST_INTEGER_EQUAL(1, s_ncalls); + XTESTS_TEST_INTEGER_EQUAL(13, s_calls[0]); +} + + +int +main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.unit.atexit.api", verbosity)) + { + /* Order matters: add-without-init must run before the single + * process init. Later cases add/drain the list; process atexit + * is then a no-op unless add-after-uninit leaves nodes (those + * are drained again before exit). + */ + XTESTS_RUN_CASE(test_add_without_init_then_uninit); + XTESTS_RUN_CASE(test_init_succeeds); + XTESTS_RUN_CASE(test_second_init_fails); + XTESTS_RUN_CASE(test_uninit_invokes_lifo); + XTESTS_RUN_CASE(test_uninit_is_idempotent_for_callbacks); + XTESTS_RUN_CASE(test_init_after_uninit_fails); + XTESTS_RUN_CASE(test_add_after_uninit_then_uninit_again); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/unit/test.unit.version/CMakeLists.txt b/test/unit/test.unit.version/CMakeLists.txt new file mode 100644 index 0000000..af5addc --- /dev/null +++ b/test/unit/test.unit.version/CMakeLists.txt @@ -0,0 +1 @@ +define_automated_test_program(test.unit.version test.unit.version.c) diff --git a/test/unit/test.unit.version/test.unit.version.c b/test/unit/test.unit.version/test.unit.version.c new file mode 100644 index 0000000..10ea1a4 --- /dev/null +++ b/test/unit/test.unit.version/test.unit.version.c @@ -0,0 +1,68 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test.unit.version.c + * + * Purpose: Unit tests for Pantheios.Extras.AtExit version macros. + * + * Created: 21st August 2026 + * Updated: 21st August 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +#include + +#include + +#include + + +static void +test_version_components(void) +{ + XTESTS_TEST_INTEGER_EQUAL(0, PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR); + XTESTS_TEST_INTEGER_EQUAL(1, PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR); + XTESTS_TEST_INTEGER_EQUAL(3, PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH); + XTESTS_TEST_INTEGER_EQUAL(0xFF, PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA); + XTESTS_TEST_INTEGER_EQUAL(PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH, PANTHEIOS_EXTRAS_ATEXIT_VER_REVISION); +} + +static void +test_version_composite(void) +{ + int const computed = + (0 + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_MAJOR << 24 ) + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_MINOR << 16 ) + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_PATCH << 8 ) + | ( PANTHEIOS_EXTRAS_ATEXIT_VER_ALPHABETA << 0 ) + ); + + XTESTS_TEST_INTEGER_EQUAL(computed, PANTHEIOS_EXTRAS_ATEXIT_VER); + XTESTS_TEST_INTEGER_EQUAL(PANTHEIOS_EXTRAS_ATEXIT_VER_0_1_3, PANTHEIOS_EXTRAS_ATEXIT_VER); + XTESTS_TEST_INTEGER_EQUAL(0x000103ff, PANTHEIOS_EXTRAS_ATEXIT_VER); +} + + +int +main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.unit.version", verbosity)) + { + XTESTS_RUN_CASE(test_version_components); + XTESTS_RUN_CASE(test_version_composite); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////// end of file //////////////////////////// */