From 3230e1d9b0689bc15ce17d3bc3533fb8d361e1e1 Mon Sep 17 00:00:00 2001 From: Edward Nolan Date: Mon, 27 Apr 2026 22:20:33 +0000 Subject: [PATCH] Refresh repository to use beman.exemplar template Migrate beman.elide from its original ad-hoc build and CI infrastructure to the standardized beman.exemplar template. Key changes: - Replace manual CMake install logic with beman_install_library from vendored infra/ package - Replace raw FetchContent calls with lockfile.json + use-fetch-content.cmake - Migrate CI from inline GitHub Actions matrix to exemplar's JSON-based reusable workflow system - Remove C++17 CI matrix entries (library requires C++20) - Add modern exemplar boilerplate: CMakePresets.json, CONTRIBUTING.md, issue templates, pre-commit configuration - Preserve all C++ source code, tests, and library-specific README content Co-Authored-By: Claude Opus 4.6 --- .clang-format | 2 +- .devcontainer/Dockerfile | 17 - .devcontainer/devcontainer.json | 18 - .devcontainer/postcreate.sh | 3 - .exemplar_version | 1 + .gitattributes | 5 + .github/CODEOWNERS | 22 +- .../implementation-deficiency.md | 35 ++ .../ISSUE_TEMPLATE/infrastructure-issues.md | 29 ++ .github/ISSUE_TEMPLATE/paper-discussion.md | 33 ++ .github/pull_request_template.md | 5 + .github/workflows/ci_tests.yml | 398 ++++++------------ .github/workflows/pre-commit-check.yml | 19 + .github/workflows/pre-commit-update.yml | 15 + .gitignore | 10 + .markdownlint.yaml | 1 + .pre-commit-config.yaml | 29 +- CMakeLists.txt | 45 +- CMakePresets.json | 135 +++++- CONTRIBUTING.md | 111 +++++ README.md | 136 ++++-- include/beman/elide/CMakeLists.txt | 3 + include/beman/elide/deduce.hpp | 17 +- include/beman/elide/elide.hpp | 120 +++--- infra/.beman_submodule | 3 + infra/.github/CODEOWNERS | 1 + .../.github}/workflows/pre-commit.yml | 2 + ...reusable-beman-create-issue-when-fault.yml | 28 ++ infra/.gitignore | 59 +++ infra/.pre-commit-config.yaml | 21 + infra/LICENSE | 219 ++++++++++ infra/README.md | 88 ++++ infra/cmake/BuildTelemetry.cmake | 4 + infra/cmake/BuildTelemetryConfig.cmake | 58 +++ infra/cmake/Config.cmake.in | 12 + .../cmake}/appleclang-toolchain.cmake | 9 +- infra/cmake/beman-install-library.cmake | 323 ++++++++++++++ {cmake => infra/cmake}/gnu-toolchain.cmake | 5 +- infra/cmake/llvm-libc++-toolchain.cmake | 20 + {cmake => infra/cmake}/llvm-toolchain.cmake | 5 +- {cmake => infra/cmake}/msvc-toolchain.cmake | 3 + infra/cmake/telemetry.sh | 118 ++++++ infra/cmake/use-fetch-content.cmake | 231 ++++++++++ lockfile.json | 13 + src/beman/elide/CMakeLists.txt | 25 -- tests/beman/elide/CMakeLists.txt | 12 +- tests/beman/elide/deduce.test.cpp | 124 ++---- tests/beman/elide/elide.test.cpp | 314 +++++++------- 48 files changed, 2122 insertions(+), 784 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/postcreate.sh create mode 100644 .exemplar_version create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/implementation-deficiency.md create mode 100644 .github/ISSUE_TEMPLATE/infrastructure-issues.md create mode 100644 .github/ISSUE_TEMPLATE/paper-discussion.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/pre-commit-check.yml create mode 100644 .github/workflows/pre-commit-update.yml create mode 100644 CONTRIBUTING.md create mode 100644 include/beman/elide/CMakeLists.txt create mode 100644 infra/.beman_submodule create mode 100644 infra/.github/CODEOWNERS rename {.github => infra/.github}/workflows/pre-commit.yml (98%) create mode 100644 infra/.github/workflows/reusable-beman-create-issue-when-fault.yml create mode 100644 infra/.gitignore create mode 100644 infra/.pre-commit-config.yaml create mode 100644 infra/LICENSE create mode 100644 infra/README.md create mode 100755 infra/cmake/BuildTelemetry.cmake create mode 100755 infra/cmake/BuildTelemetryConfig.cmake create mode 100644 infra/cmake/Config.cmake.in rename {cmake => infra/cmake}/appleclang-toolchain.cmake (82%) create mode 100644 infra/cmake/beman-install-library.cmake rename {cmake => infra/cmake}/gnu-toolchain.cmake (83%) create mode 100644 infra/cmake/llvm-libc++-toolchain.cmake rename {cmake => infra/cmake}/llvm-toolchain.cmake (84%) rename {cmake => infra/cmake}/msvc-toolchain.cmake (90%) create mode 100755 infra/cmake/telemetry.sh create mode 100644 infra/cmake/use-fetch-content.cmake create mode 100644 lockfile.json delete mode 100644 src/beman/elide/CMakeLists.txt diff --git a/.clang-format b/.clang-format index 05baf03..74f95dc 100644 --- a/.clang-format +++ b/.clang-format @@ -125,7 +125,7 @@ IndentCaseBlocks: false IndentCaseLabels: false IndentExternBlock: AfterExternBlock IndentGotoLabels: true -IndentPPDirectives: None +IndentPPDirectives: BeforeHash IndentRequiresClause: true IndentWidth: 4 IndentWrappedFunctionNames: false diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 818d35b..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/cpp:1-ubuntu-22.04 - -USER vscode - -# Install latest cmake -RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null -RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null -RUN sudo apt-get update && sudo apt-get install -y cmake - -# Install pre-commit -RUN sudo apt-get install -y python3-pip && pip3 install pre-commit - -# Avoid ASAN Stalling -# See: https://github.com/google/sanitizers/issues/1614 -# Alternative is to update to above clang-18 and gcc-13.2 -# Maybe crashing codespace??? -RUN sudo sysctl -w vm.mmap_rnd_bits=28 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 07344f6..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,18 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/cpp - -{ - "name": "Beman Project Generic Devcontainer", - "build": { - "dockerfile": "Dockerfile" - }, - "postCreateCommand": "bash .devcontainer/postcreate.sh", - "customizations": { - "vscode": { - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools" - ] - } - } -} diff --git a/.devcontainer/postcreate.sh b/.devcontainer/postcreate.sh deleted file mode 100644 index bddba03..0000000 --- a/.devcontainer/postcreate.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Setup pre-commit -pre-commit -pre-commit install diff --git a/.exemplar_version b/.exemplar_version new file mode 100644 index 0000000..447909a --- /dev/null +++ b/.exemplar_version @@ -0,0 +1 @@ +ab5c7c0cbf1f67eb43b7be9c2d18acd4d6de1ea4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..793dce7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +infra/** linguist-vendored +cookiecutter/** linguist-vendored +*.bib -linguist-detectable +*.tex -linguist-detectable +papers/* linguist-documentation diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5463655..6b8dfea 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,23 +1,3 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# Codeowners for reviews on PRs -# Note(river): -# **Please understand how codeowner file work before uncommenting anything in this section:** -# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -# -# For projects using exemplar as a template and intend to reuse its infrastructure, -# River (@wusatosi) helped create most of the original infrastructure under the scope described below, -# they are more than happy to help out with any PRs downstream, -# as well as to sync any useful change upstream to exemplar. -# -# Github Actions: -# .github/workflows/ @wusatosi # Add other project owners here -# -# Devcontainer: -# .devcontainer/ @wusatosi # Add other project owners here -# -# Pre-commit: -# .pre-commit-config.yaml @wusatosi # Add other project owners here -# .markdownlint.yaml @wusatosi # Add other project owners here - -* @ednolan @RobertLeahy +* @RobertLeahy diff --git a/.github/ISSUE_TEMPLATE/implementation-deficiency.md b/.github/ISSUE_TEMPLATE/implementation-deficiency.md new file mode 100644 index 0000000..4a0ee77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/implementation-deficiency.md @@ -0,0 +1,35 @@ +--- +name: Implementation Deficiency +about: Report a bug or performance issue of our implementation +title: '' +labels: bug +assignees: '' + +--- + + + +## Describe the deficiency + +A clear and concise description of what the deficiency is. +Link all relevant issues. +This could be a bug, or a performance problem. + +## To Reproduce + +```c++ +// Use case +``` + +## Expected Behavior + +A clear and concise description of what you expected to happen. + +## Additional Discussions + +Add any other context about the problem here. +If you believe your issue is platform dependent, +please post your compiler versions here. diff --git a/.github/ISSUE_TEMPLATE/infrastructure-issues.md b/.github/ISSUE_TEMPLATE/infrastructure-issues.md new file mode 100644 index 0000000..11fbd13 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/infrastructure-issues.md @@ -0,0 +1,29 @@ +--- +name: Infrastructure Issues +about: Report a bug or feature request with our Infrastructure +title: '' +labels: infra +assignees: '' + +--- + + + +## I am attempting to + +Describe what you were attempting to do. + +## Expected Behavior + +A clear and concise description of what you expected to happen. + +## Current Behavior + +A clear and concise description of what actually happened. + +## Additional Discussions + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/paper-discussion.md b/.github/ISSUE_TEMPLATE/paper-discussion.md new file mode 100644 index 0000000..14c9e4f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/paper-discussion.md @@ -0,0 +1,33 @@ +--- +name: Paper Discussion +about: Provide feedback to current API +title: '' +labels: '' +assignees: '' + +--- + + + +## Use case + +Describe your concerns about adding this change to the C++ Standard Library. + +```c++ +// example snippet +``` + +## What I like + +Let us know what you find positive about current approach / design. + +## What I dislike + +Let us know what you find negative about current approach / design. + +## Discussion + +Let us know if you have any more remarks. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..071cb28 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,5 @@ + diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 0d5f8ba..6f0ef58 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -4,275 +4,147 @@ name: Continuous Integration Tests on: push: + branches: + - main pull_request: - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: schedule: - - cron: '30 15 * * *' + - cron: '37 16 * * 1' jobs: - preset-test: - strategy: - matrix: - presets: - - preset: "gcc-debug" - platform: "ubuntu-latest" - - preset: "gcc-release" - platform: "ubuntu-latest" - - preset: "appleclang-debug" - platform: "macos-latest" - - preset: "appleclang-release" - platform: "macos-latest" - - preset: "msvc-debug" - platform: "windows-latest" - - preset: "msvc-release" - platform: "windows-latest" - name: "Preset: ${{ matrix.presets.preset }} on ${{ matrix.presets.platform }}" - runs-on: ${{ matrix.presets.platform }} - steps: - - uses: actions/checkout@v4 - - name: Setup build environment - uses: lukka/get-cmake@latest - with: - cmakeVersion: "~3.25.0" - ninjaVersion: "^1.11.1" - - name: Setup MSVC - if: startsWith(matrix.presets.platform, 'windows') - uses: TheMrMilchmann/setup-msvc-dev@v3 - with: - arch: x64 - - name: Run preset - run: cmake --workflow --preset ${{ matrix.presets.preset }} - - gtest-test: - strategy: - fail-fast: false - matrix: - platform: - - description: "Ubuntu GNU" - os: ubuntu-latest - toolchain: "cmake/gnu-toolchain.cmake" - - description: "Ubuntu LLVM" - os: ubuntu-latest - toolchain: "cmake/llvm-toolchain.cmake" - - description: "Windows MSVC" - os: windows-latest - toolchain: "cmake/msvc-toolchain.cmake" - - description: "Macos Appleclang" - os: macos-latest - toolchain: "cmake/appleclang-toolchain.cmake" - cpp_version: [20, 23, 26] - cmake_args: - - description: "Default" - - description: "TSan" - args: "-DBEMAN_BUILDSYS_SANITIZER=TSan" - - description: "MaxSan" - args: "-DBEMAN_BUILDSYS_SANITIZER=MaxSan" - include: - - platform: - description: "Ubuntu GCC" - os: ubuntu-latest - toolchain: "cmake/gnu-toolchain.cmake" - cpp_version: 20 - cmake_args: - description: "Werror" - args: "-DCMAKE_CXX_FLAGS='-Werror=all -Werror=extra'" - - platform: - description: "Ubuntu GCC" - os: ubuntu-latest - toolchain: "cmake/gnu-toolchain.cmake" - cpp_version: 20 - cmake_args: - description: "Dynamic" - args: "-DBUILD_SHARED_LIBS=on" - exclude: - # MSVC does not support thread sanitizer - - platform: - description: "Windows MSVC" - cmake_args: - description: "TSan" - - name: "Unit: ${{ matrix.platform.description }} ${{ matrix.cpp_version }} ${{ matrix.cmake_args.description }}" - runs-on: ${{ matrix.platform.os }} - steps: - - uses: actions/checkout@v4 - - name: Install Ninja - uses: lukka/get-cmake@latest - with: - cmakeVersion: "~3.25.0" - ninjaVersion: "^1.11.1" - - name: Setup MSVC - if: startsWith(matrix.platform.os, 'windows') - uses: TheMrMilchmann/setup-msvc-dev@v3 - with: - arch: x64 - - name: Setup Macos - if: startsWith(matrix.platform.os, 'macos') - run: sudo chmod -R 777 /opt/ - - name: Print installed softwares - shell: bash - run: | - echo "Build system:" - cmake --version - ninja --version - - name: Configure CMake - run: | - cmake -B build -S . -DCMAKE_CXX_STANDARD=${{ matrix.cpp_version }} -DCMAKE_TOOLCHAIN_FILE="${{ matrix.platform.toolchain }}" ${{ matrix.cmake_args.args }} - env: - CMAKE_GENERATOR: "Ninja Multi-Config" - - name: Build Release - run: | - # Portable commands only - cmake --build build --config Release --parallel --verbose - cmake --build build --config Release --target all_verify_interface_header_sets - cmake --install build --config Release --prefix /opt/beman.elide - ls -R /opt/beman.elide - - name: Test Release - run: ctest --test-dir build --build-config Release - - name: Build Debug - run: | - # Portable commands only - cmake --build build --config Debug --parallel --verbose - cmake --build build --config Debug --target all_verify_interface_header_sets - cmake --install build --config Debug --prefix /opt/beman.elide - ls -R /opt/beman.elide - - name: Test Debug - run: ctest --test-dir build --build-config Debug - - configuration-test: - runs-on: ubuntu-latest - strategy: - matrix: - args: - - name: "Disable build testing" - arg: "-DBEMAN_ELIDE_BUILD_TESTS=OFF" - name: "CMake: ${{ matrix.args.name }}" - steps: - - uses: actions/checkout@v4 - - name: Setup build environment - uses: lukka/get-cmake@latest - with: - cmakeVersion: "~3.25.0" - ninjaVersion: "^1.11.1" - - name: Print installed softwares - run: | - cmake --version - ninja --version - - name: Configure CMake - run: | - cmake -B build -S . -DCMAKE_CXX_STANDARD=20 ${{ matrix.args.arg }} - env: - CMAKE_GENERATOR: "Ninja Multi-Config" - - name: Build Release - run: | - # Portable commands only - cmake --build build --config Release --parallel --verbose - cmake --build build --config Release --target all_verify_interface_header_sets - cmake --install build --config Release --prefix /opt/beman.elide - ls -R /opt/beman.elide - - name: Build Debug - run: | - # Portable commands only - cmake --build build --config Debug --parallel --verbose - cmake --build build --config Debug --target all_verify_interface_header_sets - cmake --install build --config Debug --prefix /opt/beman.elide - ls -R /opt/beman.elide - - compiler-test: - runs-on: ubuntu-24.04 - strategy: - matrix: - compilers: - - class: GNU - version: 14 - - class: GNU - version: 13 - - class: GNU - version: 12 - - class: LLVM - version: 20 - - class: LLVM - version: 19 - - class: LLVM - version: 18 - - class: LLVM - version: 17 - name: "Compiler: ${{ matrix.compilers.class }} ${{ matrix.compilers.version }}" - steps: - - uses: actions/checkout@v4 - - name: Setup build environment - uses: lukka/get-cmake@latest - with: - cmakeVersion: "~3.25.0" - ninjaVersion: "^1.11.1" - - name: Install Compiler - id: install-compiler - run: | - if [ "${{ matrix.compilers.class }}" = "GNU" ]; then - CC=gcc-${{ matrix.compilers.version }} - CXX=g++-${{ matrix.compilers.version }} - - sudo add-apt-repository universe - sudo apt-get update - sudo apt-get install -y $CC - sudo apt-get install -y $CXX - - $CC --version - $CXX --version - else - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo bash llvm.sh ${{ matrix.compilers.version }} - - CC=clang-${{ matrix.compilers.version }} - CXX=clang++-${{ matrix.compilers.version }} - - $CC --version - $CXX --version - fi - - echo "CC=$CC" >> "$GITHUB_OUTPUT" - echo "CXX=$CXX" >> "$GITHUB_OUTPUT" - - name: Configure CMake - run: | - cmake -B build -S . -DCMAKE_CXX_STANDARD=20 - env: - CC: ${{ steps.install-compiler.outputs.CC }} - CXX: ${{ steps.install-compiler.outputs.CXX }} - CMAKE_GENERATOR: "Ninja Multi-Config" - - name: Build Debug - run: | - cmake --build build --config Debug --verbose - cmake --build build --config Debug --target all_verify_interface_header_sets - cmake --install build --config Debug --prefix /opt/beman.elide - find /opt/beman.elide -type f - - name: Test Debug - run: ctest --test-dir build --build-config Debug + beman-submodule-check: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.5.3 + preset-test: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.5.3 + with: + matrix_config: > + [ + {"preset": "gcc-debug", "image": "ghcr.io/bemanproject/infra-containers-gcc:latest"}, + {"preset": "gcc-release", "image": "ghcr.io/bemanproject/infra-containers-gcc:latest"}, + {"preset": "llvm-debug", "image": "ghcr.io/bemanproject/infra-containers-clang:latest"}, + {"preset": "llvm-release", "image": "ghcr.io/bemanproject/infra-containers-clang:latest"}, + {"preset": "appleclang-debug", "runner": "macos-latest"}, + {"preset": "appleclang-release", "runner": "macos-latest"}, + {"preset": "msvc-debug", "runner": "windows-latest"}, + {"preset": "msvc-release", "runner": "windows-latest"} + ] + + build-and-test: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.5.3 + with: + matrix_config: > + { + "gcc": [ + { "versions": ["15"], + "tests": [ + { "cxxversions": ["c++26"], + "tests": [ + { "stdlibs": ["libstdc++"], + "tests": [ + "Debug.Default", "Release.Default", "Release.TSan", + "Release.MaxSan", "Debug.Werror", + "Debug.Coverage" + ] + } + ] + }, + { "cxxversions": ["c++23", "c++20"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { "versions": ["14", "13"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { + "versions": ["12", "11"], + "tests": [ + { "cxxversions": ["c++23", "c++20"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "clang": [ + { "versions": ["22"], + "tests": [ + {"cxxversions": ["c++26"], + "tests": [ + { "stdlibs": ["libstdc++", "libc++"], + "tests": [ + "Debug.Default", "Release.Default", "Release.TSan", + "Release.MaxSan", "Debug.Werror" + ] + } + ] + }, + { "cxxversions": ["c++23", "c++20"], + "tests": [ + {"stdlibs": ["libstdc++", "libc++"], "tests": ["Release.Default"]} + ] + } + ] + }, + { "versions": ["21", "20", "19"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [ + {"stdlibs": ["libstdc++", "libc++"], "tests": ["Release.Default"]} + ] + } + ] + }, + { "versions": ["18"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [{"stdlibs": ["libc++"], "tests": ["Release.Default"]}] + }, + { "cxxversions": ["c++23", "c++20"], + "tests": [{"stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { "versions": ["17"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [{"stdlibs": ["libc++"], "tests": ["Release.Default"]}] + }, + { "cxxversions": ["c++20"], + "tests": [{"stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "appleclang": [ + { "versions": ["latest"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [{ "stdlibs": ["libc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "msvc": [ + { "versions": ["latest"], + "tests": [ + { "cxxversions": ["c++23"], + "tests": [ + { "stdlibs": ["stl"], + "tests": ["Debug.Default", "Release.Default", "Release.MaxSan"] + } + ] + } + ] + } + ] + } create-issue-when-fault: - runs-on: ubuntu-latest - needs: [preset-test, gtest-test, configuration-test, compiler-test] + needs: [preset-test, build-and-test] if: failure() && github.event_name == 'schedule' - steps: - # See https://github.com/cli/cli/issues/5075 - - uses: actions/checkout@v4 - - name: Create issue - run: | - issue_num=$(gh issue list -s open -S "[SCHEDULED-BUILD] Build & Test failure" -L 1 --json number | jq 'if length == 0 then -1 else .[0].number end') - - body="**Build-and-Test Failure Report** - - **Time of Failure**: $(date -u '+%B %d, %Y, %H:%M %Z') - - **Commit**: [${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) - - **Action Run**: [View logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - The scheduled build-and-test triggered by cron has failed. - Please investigate the logs and recent changes associated with this commit or rerun the workflow if you believe this is an error." - - if [[ $issue_num -eq -1 ]]; then - gh issue create --repo ${{ github.repository }} --title "[SCHEDULED-BUILD] Build & Test failure" --body "$body" - else - gh issue comment --repo ${{ github.repository }} $issue_num --body "$body" - fi - env: - GH_TOKEN: ${{ github.token }} + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.5.3 diff --git a/.github/workflows/pre-commit-check.yml b/.github/workflows/pre-commit-check.yml new file mode 100644 index 0000000..980f6c5 --- /dev/null +++ b/.github/workflows/pre-commit-check.yml @@ -0,0 +1,19 @@ +name: Lint Check (pre-commit) + +on: + # We have to use pull_request_target here as pull_request does not grant + # enough permission for reviewdog + pull_request_target: + push: + branches: + - main + +permissions: + contents: read + checks: write + issues: write + pull-requests: write + +jobs: + pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.5.3 diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml new file mode 100644 index 0000000..08e2c03 --- /dev/null +++ b/.github/workflows/pre-commit-update.yml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +name: Weekly pre-commit autoupdate + +on: + workflow_dispatch: + schedule: + - cron: "23 16 * * 4" + +jobs: + auto-update-pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.5.3 + secrets: + APP_ID: ${{ secrets.AUTO_PR_BOT_APP_ID }} + PRIVATE_KEY: ${{ secrets.AUTO_PR_BOT_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore index 286a38e..d293e3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/.cache /compile_commands.json /build + +# ignore emacs temp files +*~ +\#*\# + +# ignore vscode settings +.vscode diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 81f5fcd..21c2849 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -7,3 +7,4 @@ MD033: false # Update the comment in .clang-format if we no-longer tie these two column limits. MD013: line_length: 119 + code_blocks: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a26bab0..eb01186 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -13,21 +13,36 @@ repos: # This brings in a portable version of clang-format. # See also: https://github.com/ssciwr/clang-format-wheel - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v18.1.8 + rev: v22.1.4 hooks: - id: clang-format types_or: [c++, c] # CMake linting and formatting - - repo: https://github.com/BlankSpruce/gersemi - rev: 0.15.1 + - repo: https://github.com/BlankSpruce/gersemi-pre-commit + rev: 0.27.2 hooks: - id: gersemi name: CMake linting + exclude: ^.*/tests/.*/data/ # Exclude test data directories # Markdown linting # Config file: .markdownlint.yaml - - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.42.0 + # Commented out to disable this by default. Uncomment to enable markdown linting. + # - repo: https://github.com/igorshubovych/markdownlint-cli + # rev: v0.42.0 + # hooks: + # - id: markdownlint + + - repo: https://github.com/codespell-project/codespell + rev: v2.4.2 + hooks: + - id: codespell + + # Beman Standard checking via beman-tidy + - repo: https://github.com/bemanproject/beman-tidy + rev: v0.3.1 hooks: - - id: markdownlint + - id: beman-tidy + +exclude: 'cookiecutter/|infra/' diff --git a/CMakeLists.txt b/CMakeLists.txt index 6329852..28d7072 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,40 +1,45 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.30...4.3) project( - beman.elide + beman.elide # CMake Project Name, which is also the name of the top-level + # targets (e.g., library, executable, etc.). DESCRIPTION "Implements proposed std::elide" LANGUAGES CXX + VERSION 0.1.0 ) -enable_testing() - # [CMAKE.SKIP_TESTS] option( BEMAN_ELIDE_BUILD_TESTS - "Enable building tests and test infrastructure. Default: ON. Values: { ON, OFF }." + "Enable building tests and test infrastructure. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." ${PROJECT_IS_TOP_LEVEL} ) -include(FetchContent) -include(GNUInstallDirs) +# for find of beman_install_library and configure_build_telemetry +include(infra/cmake/beman-install-library.cmake) +include(infra/cmake/BuildTelemetryConfig.cmake) -if(BEMAN_ELIDE_BUILD_TESTS) - # Fetch GoogleTest - FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG - f8d7d77c06936315286eb55f8de22cd23c188571 # release-1.14.0 - EXCLUDE_FROM_ALL - ) - set(INSTALL_GTEST OFF) # Disable GoogleTest installation - FetchContent_MakeAvailable(googletest) -endif() +add_library(beman.elide INTERFACE) +add_library(beman::elide ALIAS beman.elide) + +target_sources( + beman.elide + PUBLIC FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +set_target_properties( + beman.elide + PROPERTIES VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL} +) + +add_subdirectory(include/beman/elide) -add_subdirectory(src/beman/elide) +beman_install_library(beman.elide TARGETS beman.elide) +configure_build_telemetry() if(BEMAN_ELIDE_BUILD_TESTS) + enable_testing() add_subdirectory(tests/beman/elide) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 8fbea46..483e1a3 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,7 +7,9 @@ "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { - "CMAKE_CXX_STANDARD": "20" + "CMAKE_CXX_STANDARD": "20", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "./infra/cmake/use-fetch-content.cmake" } }, { @@ -33,7 +35,7 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "cmake/gnu-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/gnu-toolchain.cmake" } }, { @@ -44,29 +46,51 @@ "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "cmake/gnu-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/gnu-toolchain.cmake" + } + }, + { + "name": "llvm-debug", + "displayName": "Clang Debug Build", + "inherits": [ + "_root-config", + "_debug-base" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-toolchain.cmake" + } + }, + { + "name": "llvm-release", + "displayName": "Clang Release Build", + "inherits": [ + "_root-config", + "_release-base" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/llvm-toolchain.cmake" } }, { "name": "appleclang-debug", - "displayName": "xcode Debug Build", + "displayName": "Appleclang Debug Build", "inherits": [ "_root-config", "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "cmake/appleclang-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, { "name": "appleclang-release", - "displayName": "xcode Release Build", + "displayName": "Appleclang Release Build", "inherits": [ "_root-config", "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "cmake/appleclang-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/appleclang-toolchain.cmake" } }, { @@ -77,7 +101,7 @@ "_debug-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "cmake/msvc-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } }, { @@ -88,34 +112,71 @@ "_release-base" ], "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "cmake/msvc-toolchain.cmake" + "CMAKE_TOOLCHAIN_FILE": "infra/cmake/msvc-toolchain.cmake" } } ], "buildPresets": [ + { + "name": "_root-build", + "hidden": true, + "jobs": 0 + }, { "name": "gcc-debug", - "configurePreset": "gcc-debug" + "configurePreset": "gcc-debug", + "inherits": [ + "_root-build" + ] }, { "name": "gcc-release", - "configurePreset": "gcc-release" + "configurePreset": "gcc-release", + "inherits": [ + "_root-build" + ] + }, + { + "name": "llvm-debug", + "configurePreset": "llvm-debug", + "inherits": [ + "_root-build" + ] + }, + { + "name": "llvm-release", + "configurePreset": "llvm-release", + "inherits": [ + "_root-build" + ] }, { "name": "appleclang-debug", - "configurePreset": "appleclang-debug" + "configurePreset": "appleclang-debug", + "inherits": [ + "_root-build" + ] }, { "name": "appleclang-release", - "configurePreset": "appleclang-release" + "configurePreset": "appleclang-release", + "inherits": [ + "_root-build" + ] }, { "name": "msvc-debug", - "configurePreset": "msvc-debug" + "configurePreset": "msvc-debug", + "inherits": [ + "_root-build" + ] }, { "name": "msvc-release", - "configurePreset": "msvc-release" + "configurePreset": "msvc-release", + "inherits": [ + "_root-build" + ] } ], "testPresets": [ @@ -140,6 +201,16 @@ "inherits": "_test_base", "configurePreset": "gcc-release" }, + { + "name": "llvm-debug", + "inherits": "_test_base", + "configurePreset": "llvm-debug" + }, + { + "name": "llvm-release", + "inherits": "_test_base", + "configurePreset": "llvm-release" + }, { "name": "appleclang-debug", "inherits": "_test_base", @@ -196,6 +267,40 @@ } ] }, + { + "name": "llvm-debug", + "steps": [ + { + "type": "configure", + "name": "llvm-debug" + }, + { + "type": "build", + "name": "llvm-debug" + }, + { + "type": "test", + "name": "llvm-debug" + } + ] + }, + { + "name": "llvm-release", + "steps": [ + { + "type": "configure", + "name": "llvm-release" + }, + { + "type": "build", + "name": "llvm-release" + }, + { + "type": "test", + "name": "llvm-release" + } + ] + }, { "name": "appleclang-debug", "steps": [ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..921aef8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,111 @@ +# Development + +## Configure and Build the Project Using CMake Presets + +The simplest way of configuring and building the project is to use [CMake +Presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html). Appropriate +presets for major compilers have been included by default. You can use `cmake +--list-presets=workflow` to see all available presets. + +Here is an example of invoking the `gcc-debug` preset: + +```shell +cmake --workflow --preset gcc-debug +``` + +Generally, there are two kinds of presets, `debug` and `release`. + +The `debug` presets are designed to aid development, so they have debuginfo and sanitizers +enabled. + +> [!NOTE] +> +> The sanitizers that are enabled vary from compiler to compiler. See the toolchain files +> under ([`infra/cmake`](infra/cmake/)) to determine the exact configuration used for each +> preset. + +The `release` presets are designed for production use, and +consequently have the highest optimization turned on (e.g. `O3`). + +## Configure and Build Manually + +If the presets are not suitable for your use case, a traditional CMake invocation will +provide more configurability. + +To configure, build and test the project manually, you can run this set of commands. Note +that this requires GoogleTest to be installed. + +```bash +cmake \ + -B build \ + -S . \ + -DCMAKE_CXX_STANDARD=20 \ + # Your extra arguments here. +cmake --build build +ctest --test-dir build +``` + +> [!IMPORTANT] +> +> Beman projects are [passive projects]( +> https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md#cmakepassive_projects), +> so you need to specify the C++ version via `CMAKE_CXX_STANDARD` when manually +> configuring the project. + +## Dependency Management + +### FetchContent + +Instead of installing the project's dependencies via a package manager, you can optionally +configure beman.elide to fetch them automatically via CMake FetchContent. + +To do so, specify +`-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake`. This will +bring in GoogleTest automatically along with any other dependency the project may require. + +Example commands: + +```shell +cmake \ + -B build \ + -S . \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake +cmake --build build +ctest --test-dir build +``` + +The file `./lockfile.json` configures the list of dependencies and versions that will be +acquired by FetchContent. + +## Project-specific configure arguments + +Project-specific options are prefixed with `BEMAN_ELIDE`. +You can see the list of available options with: + +```bash +cmake -LH -S . -B build | grep "BEMAN_ELIDE" -C 2 +``` + +
+ +Some project-specific configure arguments + +### `BEMAN_ELIDE_BUILD_TESTS` + +Enable building tests and test infrastructure. Default: `ON`. +Values: `{ ON, OFF }`. + +### `BEMAN_ELIDE_BUILD_EXAMPLES` + +Enable building examples. Default: `ON`. Values: `{ ON, OFF }`. + +### `BEMAN_ELIDE_INSTALL_CONFIG_FILE_PACKAGE` + +Enable installing the CMake config file package. Default: `ON`. +Values: `{ ON, OFF }`. + +This is required so that users of `beman.elide` can use +`find_package(beman.elide)` to locate the library. + +
diff --git a/README.md b/README.md index 0fd5b36..a97042d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,20 @@ +# beman.elide: Implementation of Proposed `std::elide` + -# beman.elide: Implementation of Proposed `std::elide` + +![Library Status](https://raw.githubusercontent.com/bemanproject/beman/refs/heads/main/images/badges/beman_badge-beman_library_under_development.svg) ![Continuous Integration Tests](https://github.com/bemanproject/elide/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/bemanproject/elide/actions/workflows/pre-commit-check.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/bemanproject/elide/badge.svg?branch=main)](https://coveralls.io/github/bemanproject/elide?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp29.svg) **Implements**: `std::elide` proposed in [`std::elide` (P3288R3)](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3288r3.html). **Status**: [Under development and not yet ready for production use.](https://github.com/bemanproject/beman/blob/main/docs/beman_library_maturity_model.md#under-development-and-not-yet-ready-for-production-use) +## License + +`beman.elide` is licensed under the Apache License v2.0 with LLVM Exceptions. + ## Usage `std::elide` is an object which: @@ -18,79 +25,118 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception When combined with emplacement APIs this allows for the creation of instances of immovable types within storage managed by `std::optional`, `std::list`, et cetera. -## Building beman.elide +## Dependencies + +### Build Environment -### Dependencies +This project requires at least the following to build: -This project has no C or C++ dependencies, -however, -it requires **C++20** or above to compile. +* A C++ compiler that conforms to the C++20 standard or greater +* CMake 3.30 or later +* (Test Only) GoogleTest -Build-time dependencies: +You can disable building tests by setting CMake option `BEMAN_ELIDE_BUILD_TESTS` to +`OFF` when configuring the project. + +### Supported Platforms + +| Compiler | Version | C++ Standards | Standard Library | +|------------|---------|---------------|-------------------| +| GCC | 15-13 | C++26-C++20 | libstdc++ | +| GCC | 12-11 | C++23, C++20 | libstdc++ | +| Clang | 22-19 | C++26-C++20 | libstdc++, libc++ | +| Clang | 18 | C++26-C++20 | libc++ | +| Clang | 18 | C++23, C++20 | libstdc++ | +| Clang | 17 | C++26-C++20 | libc++ | +| Clang | 17 | C++20 | libstdc++ | +| AppleClang | latest | C++26-C++20 | libc++ | +| MSVC | latest | C++23 | MSVC STL | + +## Development + +See the [Contributing Guidelines](CONTRIBUTING.md). + +## Integrate beman.elide into your project -- `cmake` -- `ninja`, `make`, or another CMake-supported build system - - CMake defaults to "Unix Makefiles" on POSIX systems +### Build -### How to build beman.elide +You can build elide using a CMake workflow preset: -```shell -cmake --workflow --preset gcc-debug +```bash cmake --workflow --preset gcc-release -cmake --install build/gcc-release --prefix /opt/beman.elide ``` -## Integrate beman.elide into your project +To list available workflow presets, you can invoke: -
- Use beman.elide directly from C++ - +```bash +cmake --list-presets=workflow +``` -If you want to use `beman.elide` from your project, -you can include `beman/elide/*.hpp` files from your C++ source files +For details on building beman.elide without using a CMake preset, refer to the +[Contributing Guidelines](CONTRIBUTING.md). -```cpp -#include +### Installation + +To install beman.elide globally after building with the `gcc-release` preset, you can +run: + +```bash +sudo cmake --install build/gcc-release ``` -```shell -# Assume /opt/beman.elide staging directory. -$ c++ -o identity_usage examples/identity_usage.cpp \ - -I /opt/beman.elide/include/ +Alternatively, to install to a prefix, for example `/opt/beman`, you can run: + +```bash +sudo cmake --install build/gcc-release --prefix /opt/beman ``` -
+This will generate the following directory structure: + +```txt +/opt/beman +├── include +│ └── beman +│ └── elide +│ ├── elide.hpp +│ └── ... +└── lib + └── cmake + └── beman.elide + ├── beman.elide-config-version.cmake + ├── beman.elide-config.cmake + └── beman.elide-targets.cmake +``` -
- Use beman.elide directly from CMake +### CMake Configuration - +If you installed beman.elide to a prefix, you can specify that prefix to your CMake +project using `CMAKE_PREFIX_PATH`; for example, `-DCMAKE_PREFIX_PATH=/opt/beman`. -For CMake based projects, you will need to use the `beman.elide` CMake module to define the `beman::elide` CMake target: +You need to bring in the `beman.elide` package to define the `beman::elide` CMake +target: ```cmake find_package(beman.elide REQUIRED) ``` -You will also need to add `beman::elide` -to the link libraries of any libraries or executables that include `beman/elide/*.hpp` in their source or header file. +You will then need to add `beman::elide` to the link libraries of any libraries or +executables that include `beman.elide` headers. ```cmake target_link_libraries(yourlib PUBLIC beman::elide) ``` -
- -
- Use beman.elide from other build systems - - +### Using beman.elide -Build systems that support `pkg-config` by providing a `beman.elide.pc` file. -Build systems that support interoperation via `pkg-config` should be able to detect `beman.elide` for you automatically. +To use `beman.elide` in your C++ project, +include an appropriate `beman.elide` header from your source code. -
- -## Contributing +```c++ +#include +``` -Issues and pull requests are appreciated. +> [!NOTE] +> +> `beman.elide` headers are to be included with the `beman/elide/` prefix. +> Altering include search paths to spell the include target another way (e.g. +> `#include `) is unsupported. diff --git a/include/beman/elide/CMakeLists.txt b/include/beman/elide/CMakeLists.txt new file mode 100644 index 0000000..60f9bce --- /dev/null +++ b/include/beman/elide/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +target_sources(beman.elide PUBLIC FILE_SET HEADERS FILES elide.hpp deduce.hpp) diff --git a/include/beman/elide/deduce.hpp b/include/beman/elide/deduce.hpp index 51cc826..c25d5e0 100644 --- a/include/beman/elide/deduce.hpp +++ b/include/beman/elide/deduce.hpp @@ -10,23 +10,20 @@ namespace beman::elide { namespace detail::deduce { -template +template struct is_elide : std::false_type {}; -template +template struct is_elide> : std::true_type {}; -} +} // namespace detail::deduce -template +template struct deduce : std::remove_cvref {}; -template - requires - detail::deduce::is_elide< - std::remove_cvref_t>::value && - std::is_invocable_v +template + requires detail::deduce::is_elide>::value && std::is_invocable_v struct deduce : std::invoke_result {}; -template +template using deduce_t = typename deduce::type; } // namespace beman::elide diff --git a/include/beman/elide/elide.hpp b/include/beman/elide/elide.hpp index 753535e..b634ba2 100644 --- a/include/beman/elide/elide.hpp +++ b/include/beman/elide/elide.hpp @@ -13,88 +13,72 @@ namespace beman::elide { namespace detail::elide { -template +template concept result = !std::same_as; -template -concept c = - std::invocable && - result> && - std::is_reference_v && - (std::is_reference_v && ...); +template +concept c = std::invocable && result> && std::is_reference_v && + (std::is_reference_v && ...); -template -concept lvalue = - std::invocable && - result>; +template +concept lvalue = std::invocable && result>; struct tombstone {}; -template +template struct lvalue_result : std::type_identity {}; -template - requires lvalue +template + requires lvalue struct lvalue_result : std::invoke_result {}; -} +} // namespace detail::elide -template - requires detail::elide::c +template + requires detail::elide::c struct elide { - elide() = delete; - elide(const elide&) = delete; - elide& operator=(const elide&) = delete; - constexpr explicit elide(T t, Args... args) noexcept - : t_(std::forward(t)), - args_(std::forward(args)...) - {} -private: - static constexpr bool rvalue_noexcept_ = - std::is_nothrow_invocable_v; - static constexpr bool lvalue_noexcept_ = - std::is_nothrow_invocable_v; - using rvalue_result_type_ = std::invoke_result_t; - using lvalue_result_type_ = typename detail::elide::lvalue_result< - T, - Args...>::type; -public: - constexpr operator rvalue_result_type_() const&& noexcept(rvalue_noexcept_) { - return std::apply( - [&](auto&&... args) noexcept(rvalue_noexcept_) { - return std::invoke( - std::forward(t_), - std::forward(args)...); - }, - args_); - } - constexpr rvalue_result_type_ operator()() const&& noexcept(rvalue_noexcept_) - { - return rvalue_result_type_(std::move(*this)); - } - constexpr operator lvalue_result_type_() const& noexcept(lvalue_noexcept_) { - if constexpr (detail::elide::lvalue) { - return std::apply( - [&](auto&&... args) noexcept(lvalue_noexcept_) { - return std::invoke(t_, args...); - }, - args_); - } else { - return {}; + elide() = delete; + elide(const elide&) = delete; + elide& operator=(const elide&) = delete; + constexpr explicit elide(T t, Args... args) noexcept + : t_(std::forward(t)), args_(std::forward(args)...) {} + + private: + static constexpr bool rvalue_noexcept_ = std::is_nothrow_invocable_v; + static constexpr bool lvalue_noexcept_ = std::is_nothrow_invocable_v; + using rvalue_result_type_ = std::invoke_result_t; + using lvalue_result_type_ = typename detail::elide::lvalue_result::type; + + public: + constexpr operator rvalue_result_type_() const&& noexcept(rvalue_noexcept_) { + return std::apply( + [&](auto&&... args) noexcept(rvalue_noexcept_) { + return std::invoke(std::forward(t_), std::forward(args)...); + }, + args_); + } + constexpr rvalue_result_type_ operator()() const&& noexcept(rvalue_noexcept_) { + return rvalue_result_type_(std::move(*this)); } - } - constexpr lvalue_result_type_ operator()() const& noexcept(lvalue_noexcept_) - requires detail::elide::lvalue - { - return rvalue_result_type_(*this); - } -private: - [[no_unique_address]] - T t_; - [[no_unique_address]] - std::tuple args_; + constexpr operator lvalue_result_type_() const& noexcept(lvalue_noexcept_) { + if constexpr (detail::elide::lvalue) { + return std::apply([&](auto&&... args) noexcept(lvalue_noexcept_) { return std::invoke(t_, args...); }, + args_); + } else { + return {}; + } + } + constexpr lvalue_result_type_ operator()() const& noexcept(lvalue_noexcept_) + requires detail::elide::lvalue + { + return rvalue_result_type_(*this); + } + + private: + [[no_unique_address]] T t_; + [[no_unique_address]] std::tuple args_; }; -template +template explicit elide(T&&, Args&&...) -> elide; } // namespace beman::elide diff --git a/infra/.beman_submodule b/infra/.beman_submodule new file mode 100644 index 0000000..56dbbcc --- /dev/null +++ b/infra/.beman_submodule @@ -0,0 +1,3 @@ +[beman_submodule] +remote=https://github.com/bemanproject/infra.git +commit_hash=ea3ef79f77fdcc378149ebc7406e81e9ceb04146 diff --git a/infra/.github/CODEOWNERS b/infra/.github/CODEOWNERS new file mode 100644 index 0000000..4ff90a4 --- /dev/null +++ b/infra/.github/CODEOWNERS @@ -0,0 +1 @@ +* @ednolan @neatudarius @rishyak @wusatosi @JeffGarland diff --git a/.github/workflows/pre-commit.yml b/infra/.github/workflows/pre-commit.yml similarity index 98% rename from .github/workflows/pre-commit.yml rename to infra/.github/workflows/pre-commit.yml index f3c4332..9646831 100644 --- a/.github/workflows/pre-commit.yml +++ b/infra/.github/workflows/pre-commit.yml @@ -5,6 +5,8 @@ on: # enough permission for reviewdog pull_request_target: push: + branches: + - main jobs: pre-commit-push: diff --git a/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml b/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml new file mode 100644 index 0000000..024a51f --- /dev/null +++ b/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +name: 'Beman issue creation workflow' +on: + workflow_call: + workflow_dispatch: +jobs: + create-issue: + runs-on: ubuntu-latest + steps: + # See https://github.com/cli/cli/issues/5075 + - uses: actions/checkout@v4 + - name: Create issue + run: | + issue_num=$(gh issue list -s open -S "[SCHEDULED-BUILD] infra repo CI job failure" -L 1 --json number | jq 'if length == 0 then -1 else .[0].number end') + body="**CI job failure Report** + - **Time of Failure**: $(date -u '+%B %d, %Y, %H:%M %Z') + - **Commit**: [${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) + - **Action Run**: [View logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + The scheduled job triggered by cron has failed. + Please investigate the logs and recent changes associated with this commit or rerun the workflow if you believe this is an error." + if [[ $issue_num -eq -1 ]]; then + gh issue create --repo ${{ github.repository }} --title "[SCHEDULED-BUILD] infra repo CI job failure" --body "$body" --assignee ${{ github.actor }} + else + gh issue comment --repo ${{ github.repository }} $issue_num --body "$body" + fi + env: + GH_TOKEN: ${{ github.token }} diff --git a/infra/.gitignore b/infra/.gitignore new file mode 100644 index 0000000..b7cdbb5 --- /dev/null +++ b/infra/.gitignore @@ -0,0 +1,59 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Python +__pycache__/ +.pytest_cache/ +*.pyc +*.pyo +*.pyd +*.pyw +*.pyz +*.pywz +*.pyzw +*.pyzwz +*.delete_me + +# MAC OS +*.DS_Store + +# Editor files +.vscode/ +.idea/ + +# Build directories +infra.egg-info/ +beman_tidy.egg-info/ +*.egg-info/ +build/ +dist/ diff --git a/infra/.pre-commit-config.yaml b/infra/.pre-commit-config.yaml new file mode 100644 index 0000000..8052e18 --- /dev/null +++ b/infra/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + + - repo: https://github.com/codespell-project/codespell + rev: v2.4.2 + hooks: + - id: codespell + + # CMake linting and formatting + - repo: https://github.com/BlankSpruce/gersemi-pre-commit + rev: 0.27.2 + hooks: + - id: gersemi + name: CMake linting + exclude: ^.*/tests/.*/data/ # Exclude test data directories diff --git a/infra/LICENSE b/infra/LICENSE new file mode 100644 index 0000000..f6db814 --- /dev/null +++ b/infra/LICENSE @@ -0,0 +1,219 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..bf9bbb0 --- /dev/null +++ b/infra/README.md @@ -0,0 +1,88 @@ +# Beman Project Infrastructure Repository + + + +This repository contains the infrastructure for The Beman Project. This is NOT a library repository, +so it does not respect the usual structure of a Beman library repository nor The Beman Standard! + +## Description + +* `cmake/`: CMake modules and toolchain files used by Beman libraries. +* `containers/`: Containers used for CI builds and tests in the Beman org. + +## Usage + +This repository is intended to be used as a beman-submodule in other Beman repositories. See +[the beman-submodule documentation](https://github.com/bemanproject/beman-submodule) for details. + + +### CMake Modules + + +#### `beman_install_library` + +The CMake modules in this repository are intended to be used by Beman libraries. Use the +`beman_add_install_library_config()` function to install your library, along with header +files, any metadata files, and a CMake config file for `find_package()` support. + +```cmake +add_library(beman.something) +add_library(beman::something ALIAS beman.something) + +# ... configure your target as needed ... + +find_package(beman-install-library REQUIRED) +beman_install_library(beman.something) +``` + +Note that the target must be created before calling `beman_install_library()`. The module +also assumes that the target is named using the `beman.something` convention, and it +uses that assumption to derive the names to match other Beman standards and conventions. +If your target does not follow that convention, raise an issue or pull request to add +more configurability to the module. + +The module will configure the target to install: + +* The library target itself +* Any public headers associated with the target +* CMake files for `find_package(beman.something)` support + +Some options for the project and target will also be supported: + +* `BEMAN_INSTALL_CONFIG_FILE_PACKAGES` - a list of package names (e.g., `beman.something`) for which to install the config file + (default: all packages) +* `_INSTALL_CONFIG_FILE_PACKAGE` - a per-project option to enable/disable config file installation (default: `ON` if the project is top-level, `OFF` otherwise). For instance for `beman.something`, the option would be `BEMAN_SOMETHING_INSTALL_CONFIG_FILE_PACKAGE`. + +# BuildTelemetry + +The cmake modules in this library provide access to CMake instrumentation data in Google Trace format which is visualizable with chrome://tracing and https://ui.perfetto.dev. + +Telemetry may be enabled in several ways: + +## `include` + +```cmake +include (infra/cmake/BuildTelemetry.cmake) +configure_build_telemetry() +``` + +## `find_package` + +```cmake +find_package(BuildTelemetry) +configure_build_telemetry() +``` + +as long as [BuildTelemetryConfig.cmake](./cmake/BuildTelemetryConfig.cmake) is in your module path. + +## `CMAKE_PROJECT_TOP_LEVEL_INCLUDES` +A non-invasive way to inject this telemetry into a CMake build you do not want to modify. +Add: +```sh +-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=infra/cmake/BuildTelemetry.cmake +``` +To the cmake invocation. + +In any form, CMake will call `telemetry.sh` which will copy the trace data in json format into a `.trace` subdirectory within the build directory. + +Multiple calls to `configure_build_telemetry` will only configure the callback hooks once, so it is safe to enable multiple times, including by TOP_LEVEL_INCLUDE. diff --git a/infra/cmake/BuildTelemetry.cmake b/infra/cmake/BuildTelemetry.cmake new file mode 100755 index 0000000..c2ff343 --- /dev/null +++ b/infra/cmake/BuildTelemetry.cmake @@ -0,0 +1,4 @@ +include_guard(GLOBAL) + +include(${CMAKE_CURRENT_LIST_DIR}/BuildTelemetryConfig.cmake) +configure_build_telemetry() diff --git a/infra/cmake/BuildTelemetryConfig.cmake b/infra/cmake/BuildTelemetryConfig.cmake new file mode 100755 index 0000000..15aae48 --- /dev/null +++ b/infra/cmake/BuildTelemetryConfig.cmake @@ -0,0 +1,58 @@ +include_guard(GLOBAL) + +set(BUILD_TELEMETRY_DIR ${CMAKE_CURRENT_LIST_DIR}) + +function(configure_build_telemetry) + if(NOT BUILD_TELEMETRY_CONFIGURATION) + # Check if the CMake version is at least 4.3 + if(CMAKE_VERSION VERSION_LESS "4.3") + message( + STATUS + "CMake version is less than 4.3, configuring cmake_instrumentation is unavailable." + ) + return() + else() + message(STATUS "Configuring Build Telemetry") + endif() + + # Find bash and jq for the telemetry callback script. + # On Windows, Git for Windows provides bash if available. + find_program(BEMAN_BASH bash) + find_program(BEMAN_JQ jq) + if(NOT BEMAN_BASH OR NOT BEMAN_JQ) + message( + STATUS + "bash or jq not found, build telemetry disabled on this platform." + ) + return() + endif() + + # Telemetry query + cmake_instrumentation( + API_VERSION 1 + DATA_VERSION 1 + OPTIONS staticSystemInformation dynamicSystemInformation trace + HOOKS + postGenerate + preBuild + postBuild + preCMakeBuild + postCMakeBuild + postCMakeInstall + postCTest + CALLBACK ${BEMAN_BASH} + ${BUILD_TELEMETRY_DIR}/telemetry.sh + ) + message( + DEBUG + "using callback script ${BUILD_TELEMETRY_DIR}/telemetry.sh via ${BEMAN_BASH}" + ) + + # Mark configuration as done in cache + set(BUILD_TELEMETRY_CONFIGURATION + TRUE + CACHE INTERNAL + "Flag to ensure Build Telemetry configured only once" + ) + endif() +endfunction(configure_build_telemetry) diff --git a/infra/cmake/Config.cmake.in b/infra/cmake/Config.cmake.in new file mode 100644 index 0000000..3f1341c --- /dev/null +++ b/infra/cmake/Config.cmake.in @@ -0,0 +1,12 @@ +# cmake/Config.cmake.in -*-makefile-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include(CMakeFindDependencyMacro) + +@BEMAN_INSTALL_FIND_DEPENDENCIES@ + +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@BEMAN_INSTALL_BASE_PKG_NAME@-targets.cmake) + +check_required_components(@BEMAN_INSTALL_BASE_PKG_NAME@) diff --git a/cmake/appleclang-toolchain.cmake b/infra/cmake/appleclang-toolchain.cmake similarity index 82% rename from cmake/appleclang-toolchain.cmake rename to infra/cmake/appleclang-toolchain.cmake index bc12103..70ef548 100644 --- a/cmake/appleclang-toolchain.cmake +++ b/infra/cmake/appleclang-toolchain.cmake @@ -16,8 +16,10 @@ include_guard(GLOBAL) -set(CMAKE_C_COMPILER clang) -set(CMAKE_CXX_COMPILER clang++) +# Prevent PATH collision with an LLVM clang installation by using the system +# compiler shims +set(CMAKE_C_COMPILER cc) +set(CMAKE_CXX_COMPILER c++) if(BEMAN_BUILDSYS_SANITIZER STREQUAL "MaxSan") set(SANITIZER_FLAGS @@ -37,3 +39,6 @@ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") set(CMAKE_C_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") + +# Add this dir to the module path so that `find_package(beman-install-library)` works +list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_LIST_DIR}") diff --git a/infra/cmake/beman-install-library.cmake b/infra/cmake/beman-install-library.cmake new file mode 100644 index 0000000..dc5a4d1 --- /dev/null +++ b/infra/cmake/beman-install-library.cmake @@ -0,0 +1,323 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include_guard(GLOBAL) + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +# beman_install_library +# ===================== +# +# Installs a library (or set of targets) along with headers, C++ modules, +# and optional CMake package configuration files. +# +# Usage: +# ------ +# beman_install_library( +# TARGETS [ ...] +# [DEPENDENCIES [ ...]] +# [NAMESPACE ] +# [EXPORT_NAME ] +# [DESTINATION ] +# ) +# +# Arguments: +# ---------- +# +# name +# Logical package name (e.g. "beman.utility"). +# Used to derive config file names and cache variable prefixes. +# +# TARGETS (required) +# List of CMake targets to install. +# +# DEPENDENCIES (optional) +# Semicolon-separated list, one dependency per entry. +# Each entry is a valid find_dependency() argument list. +# Note: you must use the bracket form for quoting if not only a package name is used! +# "[===[beman.inplace_vector 1.0.0]===] [===[beman.scope 0.0.1 EXACT]===] fmt" +# +# NAMESPACE (optional) +# Namespace for exported targets. +# Defaults to "beman::". +# +# EXPORT_NAME (optional) +# Name of the CMake export set. +# Defaults to "-targets". +# +# DESTINATION (optional) +# The install destination for CXX_MODULES. +# Defaults to ${CMAKE_INSTALL_LIBDIR}/cmake/${name}/modules. +# +# Brief +# ----- +# +# This function installs the specified project TARGETS and its FILE_SET +# HEADERS to the default CMAKE install destination. +# +# It also handles the installation of the CMake config package files if +# needed. If the given targets has a PUBLIC FILE_SET CXX_MODULE, it will also +# installed to the given DESTINATION +# +# Cache variables: +# ---------------- +# +# BEMAN_INSTALL_CONFIG_FILE_PACKAGES +# List of package names for which config files should be installed. +# +# _INSTALL_CONFIG_FILE_PACKAGE +# Per-package override to enable/disable config file installation. +# is the uppercased package name with dots replaced by underscores. +# +# Caveats +# ------- +# +# **Only one `PUBLIC FILE_SET CXX_MODULES` is yet supported to install with this +# function!** +# +# **Only header files contained in a `PUBLIC FILE_SET TYPE HEADERS` will be +# install with this function!** + +function(beman_install_library name) + # ---------------------------- + # Argument parsing + # ---------------------------- + set(oneValueArgs NAMESPACE EXPORT_NAME DESTINATION) + set(multiValueArgs TARGETS DEPENDENCIES) + + cmake_parse_arguments( + BEMAN_INSTALL + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + if(NOT BEMAN_INSTALL_TARGETS) + message( + FATAL_ERROR + "beman_install_library(${name}): TARGETS must be specified" + ) + endif() + + if(CMAKE_SKIP_INSTALL_RULES) + message( + WARNING + "beman_install_library(${name}): not installing targets '${BEMAN_INSTALL_TARGETS}' due to CMAKE_SKIP_INSTALL_RULES" + ) + return() + endif() + + set(_config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${name}") + + # ---------------------------- + # Defaults + # ---------------------------- + if(NOT BEMAN_INSTALL_NAMESPACE) + set(BEMAN_INSTALL_NAMESPACE "beman::") + endif() + + if(NOT BEMAN_INSTALL_EXPORT_NAME) + set(BEMAN_INSTALL_EXPORT_NAME "${name}-targets") + endif() + + if(NOT BEMAN_INSTALL_DESTINATION) + set(BEMAN_INSTALL_DESTINATION "${_config_install_dir}/modules") + endif() + + string(REPLACE "beman." "" install_component_name "${name}") + message( + VERBOSE + "beman-install-library(${name}): COMPONENT '${install_component_name}'" + ) + + # -------------------------------------------------- + # Install each target with all of its file sets + # -------------------------------------------------- + foreach(_tgt IN LISTS BEMAN_INSTALL_TARGETS) + if(NOT TARGET "${_tgt}") + message( + WARNING + "beman_install_library(${name}): '${_tgt}' is not a target" + ) + continue() + endif() + + # Given foo.bar, the component name is bar + string(REPLACE "." ";" name_parts "${_tgt}") + # fail if the name doesn't look like foo.bar + list(LENGTH name_parts name_parts_length) + if(NOT name_parts_length EQUAL 2) + message( + FATAL_ERROR + "beman_install_library(${name}): expects a name of the form 'beman.', got '${_tgt}'" + ) + endif() + list(GET name_parts -1 component_name) + set_target_properties( + "${_tgt}" + PROPERTIES EXPORT_NAME "${component_name}" + ) + message( + VERBOSE + "beman_install_library(${name}): EXPORT_NAME ${component_name} for TARGET '${_tgt}'" + ) + + # Get the list of interface header sets, exact one expected! + set(_install_header_set_args) + get_target_property( + _available_header_sets + ${_tgt} + INTERFACE_HEADER_SETS + ) + if(_available_header_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has INTERFACE_HEADER_SETS=${_available_header_sets}" + ) + foreach(_install_header_set IN LISTS _available_header_sets) + list( + APPEND _install_header_set_args + FILE_SET + "${_install_header_set}" + COMPONENT + "${install_component_name}_Development" + ) + endforeach() + else() + set(_install_header_set_args FILE_SET HEADERS) # Note: empty FILE_SET in this case! CK + endif() + + # Detect presence of PUBLIC C++ module file sets. Note: exact one is expected! + get_target_property(_module_sets "${_tgt}" INTERFACE_CXX_MODULE_SETS) + if(_module_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has INTERFACE_CXX_MODULE_SETS=${_module_sets}" + ) + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_INSTALL_EXPORT_NAME} + ARCHIVE COMPONENT "${install_component_name}_Development" + LIBRARY + COMPONENT "${install_component_name}_Runtime" + NAMELINK_COMPONENT "${install_component_name}_Development" + RUNTIME COMPONENT "${install_component_name}_Runtime" + ${_install_header_set_args} + FILE_SET ${_module_sets} + DESTINATION "${BEMAN_INSTALL_DESTINATION}" + COMPONENT "${install_component_name}_Development" + # NOTE: There's currently no convention for this location! CK + CXX_MODULES_BMI + DESTINATION + ${_config_install_dir}/bmi-${CMAKE_CXX_COMPILER_ID}_$ + COMPONENT "${install_component_name}_Development" + ) + else() + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_INSTALL_EXPORT_NAME} + ARCHIVE COMPONENT "${install_component_name}_Development" + LIBRARY + COMPONENT "${install_component_name}_Runtime" + NAMELINK_COMPONENT "${install_component_name}_Development" + RUNTIME COMPONENT "${install_component_name}_Runtime" + ${_install_header_set_args} + ) + endif() + endforeach() + + # -------------------------------------------------- + # Export targets + # -------------------------------------------------- + # gersemi: off + install( + EXPORT ${BEMAN_INSTALL_EXPORT_NAME} + NAMESPACE ${BEMAN_INSTALL_NAMESPACE} + CXX_MODULES_DIRECTORY cxx-modules + DESTINATION ${_config_install_dir} + COMPONENT "${install_component_name}_Development" + ) + # gersemi: on + + # ---------------------------------------- + # Config file installation logic + # + # Precedence (highest to lowest): + # 1. Per-package variable _INSTALL_CONFIG_FILE_PACKAGE + # 2. Allow-list BEMAN_INSTALL_CONFIG_FILE_PACKAGES (if defined) + # 3. Default: ON + # ---------------------------------------- + string(TOUPPER "${name}" _pkg_upper) + string(REPLACE "." "_" _pkg_prefix "${_pkg_upper}") + + option( + ${_pkg_prefix}_INSTALL_CONFIG_FILE_PACKAGE + "Enable creating and installing a CMake config-file package. Default: ON. Values: { ON, OFF }." + ON + ) + + set(_pkg_var "${_pkg_prefix}_INSTALL_CONFIG_FILE_PACKAGE") + + # Default: install config files + set(_install_config ON) + + # If the allow-list is defined, only install for packages in the list + if(DEFINED BEMAN_INSTALL_CONFIG_FILE_PACKAGES) + if(NOT "${name}" IN_LIST BEMAN_INSTALL_CONFIG_FILE_PACKAGES) + set(_install_config OFF) + endif() + endif() + + # Per-package override takes highest precedence + if(DEFINED ${_pkg_var}) + set(_install_config ${${_pkg_var}}) + endif() + + # ---------------------------------------- + # expand dependencies + # ---------------------------------------- + set(_beman_find_deps "") + foreach(dep IN LISTS BEMAN_INSTALL_DEPENDENCIES) + message( + VERBOSE + "beman-install-library(${name}): Add find_dependency(${dep})" + ) + string(APPEND _beman_find_deps "find_dependency(${dep})\n") + endforeach() + set(BEMAN_INSTALL_FIND_DEPENDENCIES "${_beman_find_deps}") + + # ---------------------------------------- + # Generate + install config files + # ---------------------------------------- + if(_install_config) + set(BEMAN_INSTALL_BASE_PKG_NAME ${name}) + configure_package_config_file( + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake" + INSTALL_DESTINATION ${_config_install_dir} + ) + + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config-version.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion + ) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config-version.cmake" + DESTINATION ${_config_install_dir} + COMPONENT "${install_component_name}_Development" + ) + else() + message( + WARNING + "beman-install-library(${name}): Not installing a config package for '${name}'" + ) + endif() +endfunction() + +set(CPACK_GENERATOR TGZ) +include(CPack) diff --git a/cmake/gnu-toolchain.cmake b/infra/cmake/gnu-toolchain.cmake similarity index 83% rename from cmake/gnu-toolchain.cmake rename to infra/cmake/gnu-toolchain.cmake index 2e2a2ad..d3b9f92 100644 --- a/cmake/gnu-toolchain.cmake +++ b/infra/cmake/gnu-toolchain.cmake @@ -20,7 +20,7 @@ set(CMAKE_CXX_COMPILER g++) if(BEMAN_BUILDSYS_SANITIZER STREQUAL "MaxSan") set(SANITIZER_FLAGS - "-fsanitize=address -fsanitize=leak -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined" + "-fsanitize=address -fsanitize=leak -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined -fsanitize-undefined-trap-on-error" ) elseif(BEMAN_BUILDSYS_SANITIZER STREQUAL "TSan") set(SANITIZER_FLAGS "-fsanitize=thread") @@ -36,3 +36,6 @@ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") set(CMAKE_C_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") + +# Add this dir to the module path so that `find_package(beman-install-library)` works +list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_LIST_DIR}") diff --git a/infra/cmake/llvm-libc++-toolchain.cmake b/infra/cmake/llvm-libc++-toolchain.cmake new file mode 100644 index 0000000..76264c6 --- /dev/null +++ b/infra/cmake/llvm-libc++-toolchain.cmake @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: BSL-1.0 + +# This toolchain file is not meant to be used directly, +# but to be invoked by CMake preset and GitHub CI. +# +# This toolchain file configures for LLVM family of compiler. +# +# BEMAN_BUILDSYS_SANITIZER: +# This optional CMake parameter is not meant for public use and is subject to +# change. +# Possible values: +# - MaxSan: configures clang and clang++ to use all available non-conflicting +# sanitizers. +# - TSan: configures clang and clang++ to enable the use of thread sanitizer. + +include(${CMAKE_CURRENT_LIST_DIR}/llvm-toolchain.cmake) + +if(NOT CMAKE_CXX_FLAGS MATCHES "-stdlib=libc\\+\\+") + string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++") +endif() diff --git a/cmake/llvm-toolchain.cmake b/infra/cmake/llvm-toolchain.cmake similarity index 84% rename from cmake/llvm-toolchain.cmake rename to infra/cmake/llvm-toolchain.cmake index d783803..f1623b7 100644 --- a/cmake/llvm-toolchain.cmake +++ b/infra/cmake/llvm-toolchain.cmake @@ -20,7 +20,7 @@ set(CMAKE_CXX_COMPILER clang++) if(BEMAN_BUILDSYS_SANITIZER STREQUAL "MaxSan") set(SANITIZER_FLAGS - "-fsanitize=address -fsanitize=leak -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined" + "-fsanitize=address -fsanitize=leak -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined -fsanitize-undefined-trap-on-error" ) elseif(BEMAN_BUILDSYS_SANITIZER STREQUAL "TSan") set(SANITIZER_FLAGS "-fsanitize=thread") @@ -36,3 +36,6 @@ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") set(CMAKE_C_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") + +# Add this dir to the module path so that `find_package(beman-install-library)` works +list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_LIST_DIR}") diff --git a/cmake/msvc-toolchain.cmake b/infra/cmake/msvc-toolchain.cmake similarity index 90% rename from cmake/msvc-toolchain.cmake rename to infra/cmake/msvc-toolchain.cmake index c2fffa7..bdc24de 100644 --- a/cmake/msvc-toolchain.cmake +++ b/infra/cmake/msvc-toolchain.cmake @@ -36,3 +36,6 @@ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") set(CMAKE_C_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") + +# Add this dir to the module path so that `find_package(beman-install-library)` works +list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_LIST_DIR}") diff --git a/infra/cmake/telemetry.sh b/infra/cmake/telemetry.sh new file mode 100755 index 0000000..307cc94 --- /dev/null +++ b/infra/cmake/telemetry.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +trap 'echo "Aborting due to errexit on line $LINENO. Exit code: $?" >&2' ERR +set -o errtrace +set -o pipefail +IFS=$'\n\t' + +############################################################################### +# Environment +############################################################################### + +# $_ME +# +# This program's basename. +_ME="$(basename "${0}")" + +############################################################################### +# Help +############################################################################### + +# _print_help() +# +# Usage: +# _print_help +# +# Print the program help information. +_print_help() { + cat <] + ${_ME} -h | --help + +Options: + -h --help Show this screen. + +Environment: + Setting DEBUG_TELEMETRY in the environment will enable DEBUG logging +HEREDOC +} + +############################################################################### +# Program Functions +############################################################################### +_debug_print() { + if [[ -n "${DEBUG_TELEMETRY:-}" ]]; then + printf "[DEBUG] $(date +'%H:%M:%S'): %s \n" "$1" >&2 + fi +} + +_check_file_exists() { + local file="$1" + if [[ ! -f "${file}" ]]; then + echo "Error: File not found: ${file}" >&2 + exit 1 # Exit the entire script with a non-zero status + fi +} + +_process_index() { + indexFile=${1:-} + _check_file_exists "${indexFile}" + _debug_print "$(cat "${indexFile}")" + + local buildDir + buildDir=$(jq -r '.buildDir' "${1:-}") + _debug_print "$(printf "buildDir is |%q|" "${buildDir}")" + + local dataDir + dataDir=$(jq -r '.dataDir' "${1:-}") + _debug_print "$(printf "dataDir is |%q|" "${dataDir}")" + + local hook + hook=$(jq -r '.hook' "${1:-}") + _debug_print "$(printf "hook is |%q|" "${hook}")" + + local trace + trace=$(jq -r '.trace' "${1:-}") + _debug_print "$(printf "trace is |%q|" "${trace}")" + + local outputDir + outputDir="${buildDir}/.trace" + _debug_print "$(printf "Copy trace to |%q|" "${outputDir}")" + mkdir -p "${outputDir}" + + local traceDestFile + traceDestFile="${outputDir}/${hook}-$(basename "${trace}")" + _debug_print "$(printf "traceDestFile: |%q|" "${traceDestFile}")" + cp "${dataDir}/${trace}" "${outputDir}/${hook}-$(basename "${trace}")" +} + +############################################################################### +# Main +############################################################################### + +# _main() +# +# Usage: +# _main [] [] +# +# Description: +# Entry point for the program, handling basic option parsing and dispatching. +_main() { + # Avoid complex option parsing when only one program option is expected. + if [[ "${1:-}" =~ ^-h|--help$ ]] + then + _print_help + else + _process_index "$@" + fi +} + +# Call `_main` after everything has been defined. +_main "$@" diff --git a/infra/cmake/use-fetch-content.cmake b/infra/cmake/use-fetch-content.cmake new file mode 100644 index 0000000..0564513 --- /dev/null +++ b/infra/cmake/use-fetch-content.cmake @@ -0,0 +1,231 @@ +cmake_minimum_required(VERSION 3.24) + +include(FetchContent) + +if(NOT BEMAN_EXEMPLAR_LOCKFILE) + set(BEMAN_EXEMPLAR_LOCKFILE + "lockfile.json" + CACHE FILEPATH + "Path to the dependency lockfile for the Beman Exemplar." + ) +endif() + +set(BemanExemplar_projectDir "${CMAKE_CURRENT_LIST_DIR}/../..") +message(TRACE "BemanExemplar_projectDir=\"${BemanExemplar_projectDir}\"") + +message(TRACE "BEMAN_EXEMPLAR_LOCKFILE=\"${BEMAN_EXEMPLAR_LOCKFILE}\"") +file( + REAL_PATH "${BEMAN_EXEMPLAR_LOCKFILE}" + BemanExemplar_lockfile + BASE_DIRECTORY "${BemanExemplar_projectDir}" + EXPAND_TILDE +) +message(DEBUG "Using lockfile: \"${BemanExemplar_lockfile}\"") + +# Force CMake to reconfigure the project if the lockfile changes +set_property( + DIRECTORY "${BemanExemplar_projectDir}" + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS "${BemanExemplar_lockfile}" +) + +# For more on the protocol for this function, see: +# https://cmake.org/cmake/help/latest/command/cmake_language.html#provider-commands +function(BemanExemplar_provideDependency method package_name) + # Read the lockfile + file(READ "${BemanExemplar_lockfile}" BemanExemplar_rootObj) + + # Get the "dependencies" field and store it in BemanExemplar_dependenciesObj + string( + JSON BemanExemplar_dependenciesObj + ERROR_VARIABLE BemanExemplar_error + GET "${BemanExemplar_rootObj}" + "dependencies" + ) + if(BemanExemplar_error) + message(FATAL_ERROR "${BemanExemplar_lockfile}: ${BemanExemplar_error}") + endif() + + # Get the length of the libraries array and store it in BemanExemplar_dependenciesObj + string( + JSON BemanExemplar_numDependencies + ERROR_VARIABLE BemanExemplar_error + LENGTH "${BemanExemplar_dependenciesObj}" + ) + if(BemanExemplar_error) + message(FATAL_ERROR "${BemanExemplar_lockfile}: ${BemanExemplar_error}") + endif() + + if(BemanExemplar_numDependencies EQUAL 0) + return() + endif() + + # Loop over each dependency object + math(EXPR BemanExemplar_maxIndex "${BemanExemplar_numDependencies} - 1") + foreach(BemanExemplar_index RANGE "${BemanExemplar_maxIndex}") + set(BemanExemplar_errorPrefix + "${BemanExemplar_lockfile}, dependency ${BemanExemplar_index}" + ) + + # Get the dependency object at BemanExemplar_index + # and store it in BemanExemplar_depObj + string( + JSON BemanExemplar_depObj + ERROR_VARIABLE BemanExemplar_error + GET "${BemanExemplar_dependenciesObj}" + "${BemanExemplar_index}" + ) + if(BemanExemplar_error) + message( + FATAL_ERROR + "${BemanExemplar_errorPrefix}: ${BemanExemplar_error}" + ) + endif() + + # Get the "name" field and store it in BemanExemplar_name + string( + JSON BemanExemplar_name + ERROR_VARIABLE BemanExemplar_error + GET "${BemanExemplar_depObj}" + "name" + ) + if(BemanExemplar_error) + message( + FATAL_ERROR + "${BemanExemplar_errorPrefix}: ${BemanExemplar_error}" + ) + endif() + + # Get the "package_name" field and store it in BemanExemplar_pkgName + string( + JSON BemanExemplar_pkgName + ERROR_VARIABLE BemanExemplar_error + GET "${BemanExemplar_depObj}" + "package_name" + ) + if(BemanExemplar_error) + message( + FATAL_ERROR + "${BemanExemplar_errorPrefix}: ${BemanExemplar_error}" + ) + endif() + + # Get the "git_repository" field and store it in BemanExemplar_repo + string( + JSON BemanExemplar_repo + ERROR_VARIABLE BemanExemplar_error + GET "${BemanExemplar_depObj}" + "git_repository" + ) + if(BemanExemplar_error) + message( + FATAL_ERROR + "${BemanExemplar_errorPrefix}: ${BemanExemplar_error}" + ) + endif() + + # Get the "git_tag" field and store it in BemanExemplar_tag + string( + JSON BemanExemplar_tag + ERROR_VARIABLE BemanExemplar_error + GET "${BemanExemplar_depObj}" + "git_tag" + ) + if(BemanExemplar_error) + message( + FATAL_ERROR + "${BemanExemplar_errorPrefix}: ${BemanExemplar_error}" + ) + endif() + + if(method STREQUAL "FIND_PACKAGE") + if(package_name STREQUAL BemanExemplar_pkgName) + string( + APPEND BemanExemplar_debug + "Redirecting find_package calls for ${BemanExemplar_pkgName} " + "to FetchContent logic.\n" + ) + string( + APPEND BemanExemplar_debug + "Fetching ${BemanExemplar_repo} at " + "${BemanExemplar_tag} according to ${BemanExemplar_lockfile}." + ) + message(DEBUG "${BemanExemplar_debug}") + FetchContent_Declare( + "${BemanExemplar_name}" + GIT_REPOSITORY "${BemanExemplar_repo}" + GIT_TAG "${BemanExemplar_tag}" + EXCLUDE_FROM_ALL + ) + + # Apply per-dependency cmake_args from the lockfile + string( + JSON BemanExemplar_cmakeArgs + ERROR_VARIABLE BemanExemplar_cmakeArgsError + GET "${BemanExemplar_depObj}" + "cmake_args" + ) + if(NOT BemanExemplar_cmakeArgsError) + string( + JSON BemanExemplar_numCmakeArgs + LENGTH "${BemanExemplar_cmakeArgs}" + ) + if(BemanExemplar_numCmakeArgs GREATER 0) + math( + EXPR + BemanExemplar_maxArgIndex + "${BemanExemplar_numCmakeArgs} - 1" + ) + foreach( + BemanExemplar_argIndex + RANGE "${BemanExemplar_maxArgIndex}" + ) + string( + JSON BemanExemplar_argKey + MEMBER "${BemanExemplar_cmakeArgs}" + "${BemanExemplar_argIndex}" + ) + string( + JSON BemanExemplar_argValue + GET "${BemanExemplar_cmakeArgs}" + "${BemanExemplar_argKey}" + ) + message( + DEBUG + "Setting ${BemanExemplar_argKey}=${BemanExemplar_argValue} for ${BemanExemplar_name}" + ) + set("${BemanExemplar_argKey}" + "${BemanExemplar_argValue}" + ) + endforeach() + endif() + endif() + + FetchContent_MakeAvailable("${BemanExemplar_name}") + + # Catch2's CTest integration module isn't on CMAKE_MODULE_PATH + # when brought in via FetchContent. Add it so that + # `include(Catch)` works. + if(BemanExemplar_pkgName STREQUAL "Catch2") + list( + APPEND CMAKE_MODULE_PATH + "${${BemanExemplar_name}_SOURCE_DIR}/extras" + ) + set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) + endif() + + # Important! _FOUND tells CMake that `find_package` is + # not needed for this package anymore + set("${BemanExemplar_pkgName}_FOUND" TRUE PARENT_SCOPE) + endif() + endif() + endforeach() +endfunction() + +cmake_language( + SET_DEPENDENCY_PROVIDER BemanExemplar_provideDependency + SUPPORTED_METHODS FIND_PACKAGE +) + +# Add this dir to the module path so that `find_package(beman-install-library)` works +list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_LIST_DIR}") diff --git a/lockfile.json b/lockfile.json new file mode 100644 index 0000000..787b905 --- /dev/null +++ b/lockfile.json @@ -0,0 +1,13 @@ +{ + "dependencies": [ + { + "name": "googletest", + "package_name": "GTest", + "git_repository": "https://github.com/google/googletest.git", + "git_tag": "6910c9d9165801d8827d628cb72eb7ea9dd538c5", + "cmake_args": { + "INSTALL_GTEST": "OFF" + } + } + ] +} diff --git a/src/beman/elide/CMakeLists.txt b/src/beman/elide/CMakeLists.txt deleted file mode 100644 index be895a9..0000000 --- a/src/beman/elide/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -add_library(beman.elide INTERFACE) -add_library(beman::elide ALIAS beman.elide) - -target_sources( - beman.elide - PUBLIC - FILE_SET HEADERS - BASE_DIRS ${PROJECT_SOURCE_DIR}/include - FILES - ${PROJECT_SOURCE_DIR}/include/beman/elide/deduce.hpp - ${PROJECT_SOURCE_DIR}/include/beman/elide/elide.hpp -) - -set_target_properties(beman.elide PROPERTIES VERIFY_INTERFACE_HEADER_SETS ON) - -install( - TARGETS beman.elide - EXPORT beman.elide - DESTINATION - $<$:debug/>${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION $<$:debug/>${CMAKE_INSTALL_BINDIR} - FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) diff --git a/tests/beman/elide/CMakeLists.txt b/tests/beman/elide/CMakeLists.txt index 1eaa621..a620323 100644 --- a/tests/beman/elide/CMakeLists.txt +++ b/tests/beman/elide/CMakeLists.txt @@ -1,21 +1,21 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -include(GoogleTest) +find_package(GTest REQUIRED) add_executable(beman.elide.tests.elide) target_sources(beman.elide.tests.elide PRIVATE elide.test.cpp) target_link_libraries( beman.elide.tests.elide - PRIVATE beman::elide GTest::gtest GTest::gtest_main + PRIVATE beman::elide GTest::gtest_main ) -gtest_add_tests(beman.elide.tests.elide "" AUTO) - add_executable(beman.elide.tests.deduce) target_sources(beman.elide.tests.deduce PRIVATE deduce.test.cpp) target_link_libraries( beman.elide.tests.deduce - PRIVATE beman::elide GTest::gtest GTest::gtest_main + PRIVATE beman::elide GTest::gtest_main ) -gtest_add_tests(beman.elide.tests.deduce "" AUTO) +include(GoogleTest) +gtest_discover_tests(beman.elide.tests.elide) +gtest_discover_tests(beman.elide.tests.deduce) diff --git a/tests/beman/elide/deduce.test.cpp b/tests/beman/elide/deduce.test.cpp index 612e1ba..1f58eba 100644 --- a/tests/beman/elide/deduce.test.cpp +++ b/tests/beman/elide/deduce.test.cpp @@ -13,36 +13,32 @@ namespace beman::elide { namespace { struct factory { - int operator()(); + int operator()(); }; struct no_lvalue_factory { - int operator()() &&; + int operator()() &&; }; struct split_factory { - int operator()() &; - double operator()() &&; + int operator()() &; + double operator()() &&; }; -template +template struct wrapper { - template - explicit wrapper(U&& u) noexcept( - std::is_nothrow_constructible_v) - : t_(std::forward(u)) - {} - constexpr const T& get() const noexcept { - return t_; - } -private: - T t_; + template + explicit wrapper(U&& u) noexcept(std::is_nothrow_constructible_v) : t_(std::forward(u)) {} + constexpr const T& get() const noexcept { return t_; } + + private: + T t_; }; -template +template explicit wrapper(T&&) -> wrapper>; -} +} // namespace static_assert(!detail::deduce::is_elide::value); static_assert(detail::deduce::is_elide>::value); @@ -53,82 +49,26 @@ static_assert(std::is_same_v>); static_assert(std::is_same_v>); static_assert(std::is_same_v>); -static_assert( - std::is_same_v< - int, - deduce_t< - elide>>); -static_assert( - std::is_same_v< - int, - deduce_t< - elide&>>); -static_assert( - std::is_same_v< - int, - deduce_t< - const elide&>>); -static_assert( - std::is_same_v< - int, - deduce_t< - elide&&>>); -static_assert( - std::is_same_v< - int, - deduce_t< - const elide&&>>); - -static_assert( - std::is_same_v< - int, - deduce_t< - elide>>); -static_assert( - std::is_same_v< - int, - deduce_t< - elide&&>>); -static_assert( - std::is_same_v< - int, - deduce_t< - const elide&&>>); -static_assert( - std::is_same_v< - elide, - deduce_t< - const elide&>>); -static_assert( - std::is_same_v< - elide, - deduce_t< - elide&>>); - -static_assert( - std::is_same_v< - double, - deduce_t< - elide>>); -static_assert( - std::is_same_v< - double, - deduce_t< - elide&&>>); -static_assert( - std::is_same_v< - int, - std::invoke_result_t< - elide&>>); -static_assert( - std::is_same_v< - int, - deduce_t< - elide&>>); +static_assert(std::is_same_v>>); +static_assert(std::is_same_v&>>); +static_assert(std::is_same_v&>>); +static_assert(std::is_same_v&&>>); +static_assert(std::is_same_v&&>>); + +static_assert(std::is_same_v>>); +static_assert(std::is_same_v&&>>); +static_assert(std::is_same_v&&>>); +static_assert(std::is_same_v, deduce_t&>>); +static_assert(std::is_same_v, deduce_t&>>); + +static_assert(std::is_same_v>>); +static_assert(std::is_same_v&&>>); +static_assert(std::is_same_v&>>); +static_assert(std::is_same_v&>>); TEST(Deduce, basic) { - wrapper w(elide([]() noexcept { return 5; })); - EXPECT_EQ(5, w.get()); + wrapper w(elide([]() noexcept { return 5; })); + EXPECT_EQ(5, w.get()); } -} +} // namespace beman::elide diff --git a/tests/beman/elide/elide.test.cpp b/tests/beman/elide/elide.test.cpp index a924eb3..d009f52 100644 --- a/tests/beman/elide/elide.test.cpp +++ b/tests/beman/elide/elide.test.cpp @@ -17,191 +17,167 @@ namespace beman::elide { namespace { struct instrumented { - struct state_type { - state_type() = default; - state_type(const state_type&) = delete; - state_type& operator=(const state_type&) = delete; - std::size_t constructed{0}; - std::size_t copy_constructed{0}; - std::size_t move_constructed{0}; - }; - explicit instrumented(state_type& state) noexcept - : state_(state) - { - ++state_.constructed; - } - instrumented(const instrumented& other) noexcept - : state_(other.state_) - { - ++state_.copy_constructed; - } - instrumented(instrumented&& other) noexcept - : state_(other.state_) - { - ++state_.move_constructed; - } -private: - state_type& state_; + struct state_type { + state_type() = default; + state_type(const state_type&) = delete; + state_type& operator=(const state_type&) = delete; + std::size_t constructed{0}; + std::size_t copy_constructed{0}; + std::size_t move_constructed{0}; + }; + explicit instrumented(state_type& state) noexcept : state_(state) { ++state_.constructed; } + instrumented(const instrumented& other) noexcept : state_(other.state_) { ++state_.copy_constructed; } + instrumented(instrumented&& other) noexcept : state_(other.state_) { ++state_.move_constructed; } + + private: + state_type& state_; }; -template +template struct factory { - struct state_type { - std::vector> lvalue_arguments; - std::vector> rvalue_arguments; - }; - explicit factory(state_type& state, instrumented::state_type& other_state) - noexcept - : state_(state), - other_state_(other_state) - {} - template - requires (std::is_constructible_v && ...) - instrumented operator()(Us&&... us) const&& { - state_.rvalue_arguments.emplace_back(std::forward(us)...); - return instrumented(other_state_); - } - template - requires (std::is_constructible_v && ...) - instrumented operator()(Us&&... us) const& { - state_.lvalue_arguments.emplace_back(std::forward(us)...); - return instrumented(other_state_); - } -private: - state_type& state_; - instrumented::state_type& other_state_; + struct state_type { + std::vector> lvalue_arguments; + std::vector> rvalue_arguments; + }; + explicit factory(state_type& state, instrumented::state_type& other_state) noexcept + : state_(state), other_state_(other_state) {} + template + requires(std::is_constructible_v && ...) + instrumented operator()(Us&&... us) const&& { + state_.rvalue_arguments.emplace_back(std::forward(us)...); + return instrumented(other_state_); + } + template + requires(std::is_constructible_v && ...) + instrumented operator()(Us&&... us) const& { + state_.lvalue_arguments.emplace_back(std::forward(us)...); + return instrumented(other_state_); + } + + private: + state_type& state_; + instrumented::state_type& other_state_; }; struct split_factory { - std::vector operator()() &; - std::vector operator()() &&; + std::vector operator()() &; + std::vector operator()() &&; }; -static_assert( - std::is_convertible_v< - elide, - std::vector>); -static_assert( - std::is_convertible_v< - elide&&, - std::vector>); -static_assert( - std::is_convertible_v< - elide&, - std::vector>); +static_assert(std::is_convertible_v, std::vector>); +static_assert(std::is_convertible_v&&, std::vector>); +static_assert(std::is_convertible_v&, std::vector>); -} +} // namespace TEST(Elide, no_arguments_lvalue_invocable) { - instrumented::state_type state; - factory<>::state_type factory_state; - const factory<> f(factory_state, state); - const elide e(std::move(f)); - static_assert(std::is_convertible_v); - static_assert(std::is_convertible_v); - EXPECT_EQ(0, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(0, factory_state.lvalue_arguments.size()); - EXPECT_EQ(0, factory_state.rvalue_arguments.size()); - (instrumented)e; - EXPECT_EQ(1, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(1, factory_state.lvalue_arguments.size()); - EXPECT_EQ(0, factory_state.rvalue_arguments.size()); - (instrumented)e; - EXPECT_EQ(2, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(2, factory_state.lvalue_arguments.size()); - EXPECT_EQ(0, factory_state.rvalue_arguments.size()); - { - instrumented res = e(); - (void)res; - } - EXPECT_EQ(3, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(3, factory_state.lvalue_arguments.size()); - EXPECT_EQ(0, factory_state.rvalue_arguments.size()); - (instrumented)std::move(e); - EXPECT_EQ(4, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(3, factory_state.lvalue_arguments.size()); - EXPECT_EQ(1, factory_state.rvalue_arguments.size()); - { - instrumented res = std::move(e)(); - (void)res; - } - EXPECT_EQ(5, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(3, factory_state.lvalue_arguments.size()); - EXPECT_EQ(2, factory_state.rvalue_arguments.size()); + instrumented::state_type state; + factory<>::state_type factory_state; + const factory<> f(factory_state, state); + const elide e(std::move(f)); + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); + EXPECT_EQ(0, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(0, factory_state.lvalue_arguments.size()); + EXPECT_EQ(0, factory_state.rvalue_arguments.size()); + (void)(instrumented)e; + EXPECT_EQ(1, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(1, factory_state.lvalue_arguments.size()); + EXPECT_EQ(0, factory_state.rvalue_arguments.size()); + (void)(instrumented)e; + EXPECT_EQ(2, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(2, factory_state.lvalue_arguments.size()); + EXPECT_EQ(0, factory_state.rvalue_arguments.size()); + { + instrumented res = e(); + (void)res; + } + EXPECT_EQ(3, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(3, factory_state.lvalue_arguments.size()); + EXPECT_EQ(0, factory_state.rvalue_arguments.size()); + (void)(instrumented)std::move(e); + EXPECT_EQ(4, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(3, factory_state.lvalue_arguments.size()); + EXPECT_EQ(1, factory_state.rvalue_arguments.size()); + { + instrumented res = std::move(e)(); + (void)res; + } + EXPECT_EQ(5, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(3, factory_state.lvalue_arguments.size()); + EXPECT_EQ(2, factory_state.rvalue_arguments.size()); } TEST(Elide, one_argument_lvalue_invocable) { - instrumented::state_type state; - factory::state_type factory_state; - const factory f(factory_state, state); - const int i = 5; - const elide e(std::move(f), i); - static_assert(std::is_convertible_v); - static_assert(std::is_convertible_v); - EXPECT_EQ(0, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(0, factory_state.lvalue_arguments.size()); - EXPECT_EQ(0, factory_state.rvalue_arguments.size()); - (instrumented)e; - EXPECT_EQ(1, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(0, factory_state.rvalue_arguments.size()); - ASSERT_EQ(1, factory_state.lvalue_arguments.size()); - EXPECT_EQ(5, std::get<0>(factory_state.lvalue_arguments.back())); - (instrumented)e; - EXPECT_EQ(2, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(0, factory_state.rvalue_arguments.size()); - EXPECT_EQ(2, factory_state.lvalue_arguments.size()); - EXPECT_EQ(5, std::get<0>(factory_state.lvalue_arguments.back())); - (instrumented)std::move(e); - EXPECT_EQ(3, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(2, factory_state.lvalue_arguments.size()); - ASSERT_EQ(1, factory_state.rvalue_arguments.size()); - EXPECT_EQ(5, std::get<0>(factory_state.rvalue_arguments.back())); + instrumented::state_type state; + factory::state_type factory_state; + const factory f(factory_state, state); + const int i = 5; + const elide e(std::move(f), i); + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); + EXPECT_EQ(0, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(0, factory_state.lvalue_arguments.size()); + EXPECT_EQ(0, factory_state.rvalue_arguments.size()); + (void)(instrumented)e; + EXPECT_EQ(1, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(0, factory_state.rvalue_arguments.size()); + ASSERT_EQ(1, factory_state.lvalue_arguments.size()); + EXPECT_EQ(5, std::get<0>(factory_state.lvalue_arguments.back())); + (void)(instrumented)e; + EXPECT_EQ(2, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(0, factory_state.rvalue_arguments.size()); + EXPECT_EQ(2, factory_state.lvalue_arguments.size()); + EXPECT_EQ(5, std::get<0>(factory_state.lvalue_arguments.back())); + (void)(instrumented)std::move(e); + EXPECT_EQ(3, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(2, factory_state.lvalue_arguments.size()); + ASSERT_EQ(1, factory_state.rvalue_arguments.size()); + EXPECT_EQ(5, std::get<0>(factory_state.rvalue_arguments.back())); } TEST(Elide, one_non_copyable_argument) { - instrumented::state_type state; - factory>::state_type factory_state; - const factory> f(factory_state, state); - auto ptr = std::make_unique(5); - const auto expected = ptr.get(); - const elide e(std::move(f), std::move(ptr)); - static_assert(std::is_convertible_v); - static_assert(!std::is_convertible_v); - EXPECT_EQ(0, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(0, factory_state.lvalue_arguments.size()); - EXPECT_EQ(0, factory_state.rvalue_arguments.size()); - (instrumented)std::move(e); - EXPECT_EQ(1, state.constructed); - EXPECT_EQ(0, state.copy_constructed); - EXPECT_EQ(0, state.move_constructed); - EXPECT_EQ(0, factory_state.lvalue_arguments.size()); - ASSERT_EQ(1, factory_state.rvalue_arguments.size()); - ASSERT_TRUE(std::get<0>(factory_state.rvalue_arguments.front())); - EXPECT_EQ( - expected, - std::get<0>(factory_state.rvalue_arguments.front()).get()); + instrumented::state_type state; + factory>::state_type factory_state; + const factory> f(factory_state, state); + auto ptr = std::make_unique(5); + const auto expected = ptr.get(); + const elide e(std::move(f), std::move(ptr)); + static_assert(std::is_convertible_v); + static_assert(!std::is_convertible_v); + EXPECT_EQ(0, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(0, factory_state.lvalue_arguments.size()); + EXPECT_EQ(0, factory_state.rvalue_arguments.size()); + (void)(instrumented)std::move(e); + EXPECT_EQ(1, state.constructed); + EXPECT_EQ(0, state.copy_constructed); + EXPECT_EQ(0, state.move_constructed); + EXPECT_EQ(0, factory_state.lvalue_arguments.size()); + ASSERT_EQ(1, factory_state.rvalue_arguments.size()); + ASSERT_TRUE(std::get<0>(factory_state.rvalue_arguments.front())); + EXPECT_EQ(expected, std::get<0>(factory_state.rvalue_arguments.front()).get()); } -} +} // namespace beman::elide