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 //////////////////////////// */