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/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index e1662e7..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,16 +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", - "image": "ghcr.io/bemanproject/devcontainers-gcc:14", - "postCreateCommand": "pre-commit", - "customizations": { - "vscode": { - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools" - ] - } - } -} 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 index 2c34f89..793dce7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +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 c4e66dc..34d0cfe 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 - -* @bretbrownjr @changkhothuychung @dietmarkuehl @steve-downey @wusatosi +* @Dragosh-C diff --git a/.github/actions/cmake-build-test/action.yml b/.github/actions/cmake-build-test/action.yml deleted file mode 100644 index 421b8c9..0000000 --- a/.github/actions/cmake-build-test/action.yml +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -name: 'CMake Build Test' -description: '' -inputs: - cpp_version: - required: true - toolchain_file: - required: true - cmake_extra_args: - description: 'extra cmake arguments' - Default: '' - disable_test: - Default: false -runs: - using: 'composite' - steps: - - name: Setup Macos - if: startsWith(matrix.platform.os, 'macos') - shell: bash - run: sudo chmod -R 777 /opt/ - - name: Print installed software - shell: bash - run: | - echo "Build system:" - cmake --version - ninja --version - - name: Configure CMake - shell: bash - run: | - cmake \ - -B build \ - -S . \ - -DCMAKE_CXX_STANDARD=${{ inputs.cpp_version }} \ - -DCMAKE_TOOLCHAIN_FILE="${{ inputs.toolchain_file }}" \ - -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="./infra/cmake/use-fetch-content.cmake" \ - ${{ matrix.cmake_args.args }} - env: - CMAKE_GENERATOR: "Ninja Multi-Config" - - name: Build Release - shell: bash - run: | - 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.package - ls -R /opt/beman.package - - name: Test Release - if: ${{ !inputs.disable_test }} - shell: bash - run: ctest --test-dir build --build-config Release - - name: Build Debug - shell: bash - run: | - 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.package - ls -R /opt/beman.package - - name: Test Debug - if: ${{ !inputs.disable_test }} - shell: bash - run: ctest --test-dir build --build-config Debug diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6085d62..071cb28 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,71 +1,5 @@ - - - - -## Description - -Please describe your contribution in a single sentence. - -## Related Issues - - - -## Motivation and Context - -Explain why this change is needed. - -## Testing - -Explain how is this tested. - -## Meta - - - -- [ ] If all approvals are obtained and the PR is green, any Beman member can merge the PR. - - diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 8ea6411..162b165 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -4,253 +4,144 @@ name: Continuous Integration Tests on: push: + branches: + - main pull_request: workflow_dispatch: schedule: - - cron: '30 15 * * *' + - cron: '1 17 * * 6' jobs: - beman-submodule-test: - runs-on: ubuntu-latest - name: "Check beman submodules for consistency" - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: beman submodule consistency check - run: | - (set -o pipefail; ./infra/tools/beman-submodule/beman-submodule status | grep -qvF '+') + beman-submodule-check: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.5.3 preset-test: - strategy: - fail-fast: false - matrix: - presets: - - preset: "gcc-debug" - platform: "ubuntu-latest" - - preset: "gcc-release" - platform: "ubuntu-latest" - - preset: "llvm-debug" - platform: "ubuntu-latest" - - preset: "llvm-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: "infra/cmake/gnu-toolchain.cmake" - - description: "Ubuntu LLVM" - os: ubuntu-latest - toolchain: "infra/cmake/llvm-toolchain.cmake" - - description: "Windows MSVC" - os: windows-latest - toolchain: "infra/cmake/msvc-toolchain.cmake" - - description: "Macos Appleclang" - os: macos-latest - toolchain: "infra/cmake/appleclang-toolchain.cmake" - cpp_version: [17, 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: "infra/cmake/gnu-toolchain.cmake" - cpp_version: 17 - cmake_args: - description: "Werror" - args: "-DCMAKE_CXX_FLAGS='-Werror=all -Werror=extra'" - - platform: - description: "Ubuntu GCC" - os: ubuntu-latest - toolchain: "infra/cmake/gnu-toolchain.cmake" - cpp_version: 17 - 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: Build and Test - uses: ./.github/actions/cmake-build-test - with: - cpp_version: ${{ matrix.cpp_version }} - toolchain_file: ${{ matrix.platform.toolchain }} - cmake_extra_args: ${{ matrix.cmake_args.args }} - - configuration-test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - args: - - name: "Disable build testing" - arg: "-DBEMAN_EXEMPLAR_BUILD_TESTS=OFF" - - name: "Disable example building" - arg: "-DBEMAN_EXEMPLAR_BUILD_EXAMPLES=OFF" - - name: "Disable config-file package creation" - arg: "-DBEMAN_EXEMPLAR_INSTALL_CONFIG_FILE_PACKAGE=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: Build and Test - uses: ./.github/actions/cmake-build-test - with: - cpp_version: 17 - toolchain_file: "infra/cmake/gnu-toolchain.cmake" - cmake_extra_args: ${{ matrix.args.arg }} - disable_test: true - - compiler-test: - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - compilers: - - class: GNU - version: 14 - toolchain: "infra/cmake/gnu-toolchain.cmake" - - class: GNU - version: 13 - toolchain: "infra/cmake/gnu-toolchain.cmake" - - class: GNU - version: 12 - toolchain: "infra/cmake/gnu-toolchain.cmake" - - class: LLVM - version: 20 - toolchain: "infra/cmake/llvm-toolchain.cmake" - - class: LLVM - version: 19 - toolchain: "infra/cmake/llvm-toolchain.cmake" - - class: LLVM - version: 18 - toolchain: "infra/cmake/llvm-toolchain.cmake" - - class: LLVM - version: 17 - toolchain: "infra/cmake/llvm-toolchain.cmake" - 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: | - sudo add-apt-repository universe - sudo apt-get update - - if [ "${{ matrix.compilers.class }}" = "GNU" ]; then - CC=gcc-${{ matrix.compilers.version }} - CXX=g++-${{ matrix.compilers.version }} - - 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: Build and Test - uses: ./.github/actions/cmake-build-test - with: - cpp_version: 20 - toolchain_file: ${{ matrix.compilers.toolchain }} + 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"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { "versions": ["14", "13"], + "tests": [ + { "cxxversions": ["c++26", "c++23"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { + "versions": ["12"], + "tests": [ + { "cxxversions": ["c++23"], + "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"], + "tests": [ + {"stdlibs": ["libstdc++", "libc++"], "tests": ["Release.Default"]} + ] + } + ] + }, + { "versions": ["21", "20", "19"], + "tests": [ + { "cxxversions": ["c++26", "c++23"], + "tests": [ + {"stdlibs": ["libstdc++", "libc++"], "tests": ["Release.Default"]} + ] + } + ] + }, + { "versions": ["18"], + "tests": [ + { "cxxversions": ["c++26", "c++23"], + "tests": [{"stdlibs": ["libc++"], "tests": ["Release.Default"]}] + }, + { "cxxversions": ["c++23"], + "tests": [{"stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { "versions": ["17"], + "tests": [ + { "cxxversions": ["c++26", "c++23"], + "tests": [{"stdlibs": ["libc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "appleclang": [ + { "versions": ["latest"], + "tests": [ + { "cxxversions": ["c++26", "c++23"], + "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..7142aef --- /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: "56 16 * * 0" + +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 25c2ab7..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,19 +13,21 @@ 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 # Commented out to disable this by default. Uncomment to enable markdown linting. # - repo: https://github.com/igorshubovych/markdownlint-cli # rev: v0.42.0 @@ -33,7 +35,7 @@ repos: # - id: markdownlint - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.2 hooks: - id: codespell @@ -42,3 +44,5 @@ repos: rev: v0.3.1 hooks: - id: beman-tidy + +exclude: 'cookiecutter/|infra/' diff --git a/CMakeLists.txt b/CMakeLists.txt index dd9f81f..3f3eb98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,53 +1,56 @@ # 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.copyable_function # CMake Project Name, which is also the name of the top-level # targets (e.g., library, executable, etc.). DESCRIPTION "A Beman library copyable_function" LANGUAGES CXX - VERSION 1.0.0 + VERSION 0.1.0 ) -set(CMAKE_CXX_STANDARD 26) - -enable_testing() # [CMAKE.SKIP_TESTS] option( BEMAN_COPYABLE_FUNCTION_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} ) +# [CMAKE.SKIP_EXAMPLES] option( - BEMAN_COPYABLE_FUNCTION_INSTALL_CONFIG_FILE_PACKAGE - "Enable creating and installing a CMake config-file package. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." + BEMAN_COPYABLE_FUNCTION_BUILD_EXAMPLES + "Enable building examples. 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_COPYABLE_FUNCTION_BUILD_TESTS) - # Fetch GoogleTest - FetchContent_Declare( - GTest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG - 6910c9d9165801d8827d628cb72eb7ea9dd538c5 # release-1.16.0 - EXCLUDE_FROM_ALL - ) - set(INSTALL_GTEST OFF) # Disable GoogleTest installation - FetchContent_MakeAvailable(GTest) -endif() +add_library(beman.copyable_function INTERFACE) +add_library(beman::copyable_function ALIAS beman.copyable_function) -add_subdirectory(src/beman/copyable_function) +target_sources( + beman.copyable_function + PUBLIC FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +set_target_properties( + beman.copyable_function + PROPERTIES VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL} +) + +add_subdirectory(include/beman/copyable_function) + +beman_install_library(beman.copyable_function TARGETS beman.copyable_function) +configure_build_telemetry() if(BEMAN_COPYABLE_FUNCTION_BUILD_TESTS) + enable_testing() add_subdirectory(tests/beman/copyable_function) endif() -if(BEMAN_COPYABLE_FUNCTION_BUILD_TESTS) +if(BEMAN_COPYABLE_FUNCTION_BUILD_EXAMPLES) add_subdirectory(examples) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 4fe1b5b..bd35911 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,7 +7,7 @@ "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { - "CMAKE_CXX_STANDARD": "26", + "CMAKE_CXX_STANDARD": "23", "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "./infra/cmake/use-fetch-content.cmake" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ad1e153 --- /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=17 \ + # 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.copyable_function 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=17 \ + -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_COPYABLE_FUNCTION`. +You can see the list of available options with: + +```bash +cmake -LH -S . -B build | grep "BEMAN_COPYABLE_FUNCTION" -C 2 +``` + +
+ +Some project-specific configure arguments + +### `BEMAN_COPYABLE_FUNCTION_BUILD_TESTS` + +Enable building tests and test infrastructure. Default: `ON`. +Values: `{ ON, OFF }`. + +### `BEMAN_COPYABLE_FUNCTION_BUILD_EXAMPLES` + +Enable building examples. Default: `ON`. Values: `{ ON, OFF }`. + +### `BEMAN_COPYABLE_FUNCTION_INSTALL_CONFIG_FILE_PACKAGE` + +Enable installing the CMake config file package. Default: `ON`. +Values: `{ ON, OFF }`. + +This is required so that users of `beman.copyable_function` can use +`find_package(beman.copyable_function)` to locate the library. + +
diff --git a/README.md b/README.md index 25c9b5b..291c481 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,19 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception --> - -![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/exemplar/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/bemanproject/exemplar/actions/workflows/pre-commit.yml/badge.svg) -![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp26.svg) - + +![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/copyable_function/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/bemanproject/copyable_function/actions/workflows/pre-commit-check.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/bemanproject/copyable_function/badge.svg?branch=main)](https://coveralls.io/github/bemanproject/copyable_function?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp26.svg) `beman.copyable_function` is a type-erased function wrapper that can represent any copyable callable matching the function signature R(Args...). The library conforms to the [The Beman Standard](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md). +**Implements**: `std::copyable_function` proposed in [P2548R6](https://wg21.link/P2548R6). + **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.copyable\_function is licensed under the Apache License v2.0 with LLVM Exceptions. +`beman.copyable_function` is licensed under the Apache License v2.0 with LLVM Exceptions. ## Usage @@ -41,247 +40,122 @@ int main() ``` +Full runnable examples can be found in [`examples/`](examples/). + ## Dependencies ### Build Environment This project requires at least the following to build: -* C++17 -* CMake 3.25 +* A C++ compiler that conforms to the C++17 standard or greater +* CMake 3.30 or later +* (Test Only) GoogleTest -This project pulls [Google Test](https://github.com/google/googletest) -from GitHub as a development dependency for its testing framework, -thus requiring an active internet connection to configure. -You can disable this behavior by setting cmake option -[`BEMAN_EXEMPLAR_BUILD_TESTS`](#beman_exemplar_build_tests) to `OFF` -when configuring the project. +You can disable building tests by setting CMake option `BEMAN_COPYABLE_FUNCTION_BUILD_TESTS` to +`OFF` when configuring the project. ### Supported Platforms -This project officially supports: - -* GNU GCC Compiler \[version 12-14\] -* LLVM Clang++ Compiler \[version 17-20\] -* AppleClang compiler on Mac OS -* MSVC compiler on Windows - -> [!NOTE] -> -> Versions outside of this range would likely work as well, -> especially if you're using a version above the given range -> (e.g. HEAD/ nightly). -> These development environments are verified using our CI configuration. +| Compiler | Version | C++ Standards | Standard Library | +|------------|---------|---------------|-------------------| +| GCC | 15-13 | C++26, C++23 | libstdc++ | +| GCC | 12 | C++23 | libstdc++ | +| Clang | 22-19 | C++26, C++23 | libstdc++, libc++ | +| Clang | 18 | C++26, C++23 | libc++ | +| Clang | 18 | C++23 | libstdc++ | +| Clang | 17 | C++26, C++23 | libc++ | +| AppleClang | latest | C++26, C++23 | libc++ | +| MSVC | latest | C++23 | MSVC STL | ## Development -### Develop using GitHub Codespace - -This project supports [GitHub Codespace](https://github.com/features/codespaces) -via [Development Containers](https://containers.dev/), -which allows rapid development and instant hacking in your browser. -We recommend you using GitHub codespace to explore this project as this -requires minimal setup. - -You can create a codespace for this project by clicking this badge: - -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/bemanproject/exemplar) - -For more detailed documentation regarding creating and developing inside of -GitHub codespaces, please reference [this doc](https://docs.github.com/en/codespaces/). - -> [!NOTE] -> -> The codespace container may take up to 5 minutes to build and spin-up, -> this is normal as we need to build a custom docker container to setup -> an environment appropriate for beman projects. - -### Develop locally on your machines - -
- For Linux based systems - -Beman libraries requires [recent versions of CMake](#build-environment), -we advice you download CMake directly from [CMake's website](https://cmake.org/download/) -or install via the [Kitware apt library](https://apt.kitware.com/). - -A [supported compiler](#supported-platforms) should be available from your package manager. -Alternatively you could use an install script from official compiler vendors. - -Here is an example of how to install the latest stable version of clang -as per [the official LLVM install guide](https://apt.llvm.org/). - -```bash -bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" -``` +See the [Contributing Guidelines](CONTRIBUTING.md). -
+## Integrate beman.copyable_function into your project -
- For MacOS based systems +### Build -Beman libraries require [recent versions of CMake](#build-environment). -You can use [`Homebrew`](https://brew.sh/) to install the latest major version of CMake. +You can build copyable_function using a CMake workflow preset: ```bash -brew install cmake +cmake --workflow --preset gcc-release ``` -A [supported compiler](#supported-platforms) is also available from brew. - -For example, you can install latest major release of Clang++ compiler as: +To list available workflow presets, you can invoke: ```bash -brew install llvm -``` - -
- -### Configure and Build the Project Using CMake Presets - -This project recommends using [CMake Presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) -to configure, build and test the project. -Appropriate presets for major compilers have been included by default. -You can use `cmake --list-presets` to see all available presets. - -Here is an example to invoke the `gcc-debug` preset. - -```shell -cmake --workflow --preset gcc-debug +cmake --list-presets=workflow ``` -Generally, there's two kinds of presets, `debug` and `release`. - -The `debug` presets are designed to aid development, so it has debugging -instrumentation enabled and as many sanitizers turned on as possible. +For details on building beman.copyable_function without using a CMake preset, refer to the +[Contributing Guidelines](CONTRIBUTING.md). -> [!NOTE] -> -> The set of sanitizer supports are different across compilers. -> You can checkout the exact set compiler arguments by looking at the toolchain -> files under the [`cmake`](cmake/) directory. - -The `release` presets are designed for use in production environments, -thus it has the highest optimization turned on (e.g. `O3`). - -### Configure and Build Manually - -While [CMake Presets](#configure-and-build-the-project-using-cmake-presets) are -convenient, you might want to set different configuration or compiler arguments -than any provided preset supports. +### Installation -To configure, build and test the project with extra arguments, -you can run this sets of command. +To install beman.copyable_function globally after building with the `gcc-release` preset, you can +run: ```bash -cmake -B build -S . -DCMAKE_CXX_STANDARD=20 # Your extra arguments here. -cmake --build build -ctest --test-dir build +sudo cmake --install build/gcc-release ``` -> [!IMPORTANT] -> -> Beman projects are -> [passive projects](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md#cmake), -> therefore, -> you will need to specify C++ version via `CMAKE_CXX_STANDARD` -> when manually configuring the project. - -### Project specific configure arguments - -When configuring the project manually, -you can pass an array of project specific CMake configs to customize your build. - -Project specific options are prefixed with `BEMAN_EXEMPLAR`. -You can see the list of available options with: +Alternatively, to install to a prefix, for example `/opt/beman`, you can run: ```bash -cmake -LH | grep "BEMAN_EXEMPLAR" -C 2 +sudo cmake --install build/gcc-release --prefix /opt/beman ``` -
- - Details of CMake arguments. +This will generate the following directory structure: -#### `BEMAN_EXEMPLAR_BUILD_TESTS` - -Enable building tests and test infrastructure. Default: ON. -Values: { ON, OFF }. - -You can configure the project to have this option turned off via: - -```bash -cmake -B build -S . -DCMAKE_CXX_STANDARD=20 -DBEMAN_EXEMPLAR_BUILD_TESTS=OFF -``` - -> [!TIP] -> Because this project requires Google Tests as part of its development -> dependency, -> disable building tests avoids the project from pulling Google Tests from -> GitHub. - -#### `BEMAN_EXEMPLAR_BUILD_EXAMPLES` - -Enable building examples. Default: ON. Values: { ON, OFF }. - -
- -## Integrate beman.copyable_function into your project - -To use `beman.copyable_function` in your C++ project, -include an appropriate `beman.copyable_function` header from your source code. - -```c++ -#include +```txt +/opt/beman +├── include +│ └── beman +│ └── copyable_function +│ ├── copyable_function.hpp +│ └── ... +└── lib + └── cmake + └── beman.copyable_function + ├── beman.copyable_function-config-version.cmake + ├── beman.copyable_function-config.cmake + └── beman.copyable_function-targets.cmake ``` -> [!NOTE] -> -> `beman.copyable_function` headers are to be included with the `beman/copyable_function/` directories prefixed. -> It is not supported to alter include search paths to spell the include target another way. For instance, -> `#include ` is not a supported interface. - -How you will link your project against `beman.copyable_function` will depend on your build system. -CMake instructions are provided in following sections. +### CMake Configuration -### Linking your project to beman.exemplar with CMake +If you installed beman.copyable_function 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.copyable_function` CMake module -to define the `beman::copyable_function` CMake target: +You need to bring in the `beman.copyable_function` package to define the `beman::copyable_function` CMake +target: ```cmake find_package(beman.copyable_function REQUIRED) ``` -You will also need to add `beman::copyable_function` to the link libraries of -any libraries or executables that include beman.copyable_function's header file. +You will then need to add `beman::copyable_function` to the link libraries of any libraries or +executables that include `beman.copyable_function` headers. ```cmake target_link_libraries(yourlib PUBLIC beman::copyable_function) ``` -### Produce beman.copyable_function static library locally +### Using beman.copyable_function -You can include copyable_function's headers locally -by producing a static `libbeman.copyable_function.a` library. +To use `beman.copyable_function` in your C++ project, +include an appropriate `beman.copyable_function` header from your source code. -```bash -cmake --workflow --preset gcc-release -cmake --install build/gcc-release --prefix /opt/beman.copyable_function +```c++ +#include ``` -This will generate such directory structure at `/opt/beman.copyable_function`. - -```txt -/opt/beman.copyable_function -├── include -│ └── beman -│ └── copyable_function -│ └── copyable_function.hpp -└── lib - └── libbeman.copyable_function.a -``` +> [!NOTE] +> +> `beman.copyable_function` headers are to be included with the `beman/copyable_function/` prefix. +> Altering include search paths to spell the include target another way (e.g. +> `#include `) is unsupported. ## Contributing diff --git a/cmake/appleclang-toolchain.cmake b/cmake/appleclang-toolchain.cmake deleted file mode 100644 index bc12103..0000000 --- a/cmake/appleclang-toolchain.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -# 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 apple clang family of compiler. -# Note this is different from LLVM toolchain. -# -# 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. Note that apple clang does not support leak sanitizer. -# - TSan: configures clang and clang++ to enable the use of thread sanitizer. - -include_guard(GLOBAL) - -set(CMAKE_C_COMPILER clang) -set(CMAKE_CXX_COMPILER clang++) - -if(BEMAN_BUILDSYS_SANITIZER STREQUAL "MaxSan") - set(SANITIZER_FLAGS - "-fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined" - ) -elseif(BEMAN_BUILDSYS_SANITIZER STREQUAL "TSan") - set(SANITIZER_FLAGS "-fsanitize=thread") -endif() - -set(CMAKE_C_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}") -set(CMAKE_CXX_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}") - -set(RELEASE_FLAGS "-O3 ${SANITIZER_FLAGS}") - -set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") - -set(CMAKE_C_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") -set(CMAKE_CXX_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") diff --git a/cmake/beman.copyable_function-config.cmake.in b/cmake/beman.copyable_function-config.cmake.in deleted file mode 100644 index 3910054..0000000 --- a/cmake/beman.copyable_function-config.cmake.in +++ /dev/null @@ -1,7 +0,0 @@ -set(BEMAN_COPYABLE_FUNCTION_VERSION @PROJECT_VERSION@) - -@PACKAGE_INIT@ - -include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) - -check_required_components(@PROJECT_NAME@) diff --git a/cmake/gnu-toolchain.cmake b/cmake/gnu-toolchain.cmake deleted file mode 100644 index 2e2a2ad..0000000 --- a/cmake/gnu-toolchain.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -# 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 GNU 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 gcc and g++ to use all available non-conflicting -# sanitizers. -# - TSan: configures gcc and g++ to enable the use of thread sanitizer - -include_guard(GLOBAL) - -set(CMAKE_C_COMPILER gcc) -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" - ) -elseif(BEMAN_BUILDSYS_SANITIZER STREQUAL "TSan") - set(SANITIZER_FLAGS "-fsanitize=thread") -endif() - -set(CMAKE_C_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}") -set(CMAKE_CXX_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}") - -set(RELEASE_FLAGS "-O3 ${SANITIZER_FLAGS}") - -set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") - -set(CMAKE_C_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") -set(CMAKE_CXX_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") diff --git a/cmake/llvm-toolchain.cmake b/cmake/llvm-toolchain.cmake deleted file mode 100644 index d783803..0000000 --- a/cmake/llvm-toolchain.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -# 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_guard(GLOBAL) - -set(CMAKE_C_COMPILER clang) -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" - ) -elseif(BEMAN_BUILDSYS_SANITIZER STREQUAL "TSan") - set(SANITIZER_FLAGS "-fsanitize=thread") -endif() - -set(CMAKE_C_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}") -set(CMAKE_CXX_FLAGS_DEBUG_INIT "${SANITIZER_FLAGS}") - -set(RELEASE_FLAGS "-O3 ${SANITIZER_FLAGS}") - -set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") - -set(CMAKE_C_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") -set(CMAKE_CXX_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") diff --git a/cmake/msvc-toolchain.cmake b/cmake/msvc-toolchain.cmake deleted file mode 100644 index c2fffa7..0000000 --- a/cmake/msvc-toolchain.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -# 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 MSVC 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 cl to use all available non-conflicting sanitizers. -# -# Note that in other toolchain files, TSan is also a possible value for -# BEMAN_BUILDSYS_SANITIZER, however, MSVC does not support thread sanitizer, -# thus this value is omitted. - -include_guard(GLOBAL) - -set(CMAKE_C_COMPILER cl) -set(CMAKE_CXX_COMPILER cl) - -if(BEMAN_BUILDSYS_SANITIZER STREQUAL "MaxSan") - # /Zi flag (add debug symbol) is needed when using address sanitizer - # See C5072: https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-c5072 - set(SANITIZER_FLAGS "/fsanitize=address /Zi") -endif() - -set(CMAKE_CXX_FLAGS_DEBUG_INIT "/EHsc /permissive- ${SANITIZER_FLAGS}") -set(CMAKE_C_FLAGS_DEBUG_INIT "/EHsc /permissive- ${SANITIZER_FLAGS}") - -set(RELEASE_FLAGS "/EHsc /permissive- /O2 ${SANITIZER_FLAGS}") - -set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "${RELEASE_FLAGS}") - -set(CMAKE_C_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") -set(CMAKE_CXX_FLAGS_RELEASE_INIT "${RELEASE_FLAGS}") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7efcfad..89230e6 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,16 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -add_executable(sample_usage sample_usage.cpp) +set(ALL_EXAMPLES sample_usage) +message("Examples to be built: ${ALL_EXAMPLES}") -target_link_libraries(sample_usage PRIVATE beman::copyable_function) +foreach(example ${ALL_EXAMPLES}) + add_executable(beman.copyable_function.examples.${example}) + target_sources( + beman.copyable_function.examples.${example} + PRIVATE ${example}.cpp + ) + target_link_libraries( + beman.copyable_function.examples.${example} + PRIVATE beman::copyable_function + ) +endforeach() diff --git a/include/beman/copyable_function/CMakeLists.txt b/include/beman/copyable_function/CMakeLists.txt new file mode 100644 index 0000000..a49b39a --- /dev/null +++ b/include/beman/copyable_function/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +target_sources( + beman.copyable_function + PUBLIC + FILE_SET HEADERS + FILES + copyable_function.hpp + copyable_function_helper.h + copyable_function_impl.hpp +) diff --git a/include/beman/copyable_function/copyable_function_helper.h b/include/beman/copyable_function/copyable_function_helper.h index 02c2d81..ecd4a77 100644 --- a/include/beman/copyable_function/copyable_function_helper.h +++ b/include/beman/copyable_function/copyable_function_helper.h @@ -1,7 +1,11 @@ #ifndef BEMAN_COPYABLE_FUNCTION_HELPER #define BEMAN_COPYABLE_FUNCTION_HELPER +#include +#include #include +#include +#include namespace beman { template struct _is_in_place_type : std::false_type {}; diff --git a/infra/.beman_submodule b/infra/.beman_submodule index 7d06f0a..56dbbcc 100644 --- a/infra/.beman_submodule +++ b/infra/.beman_submodule @@ -1,3 +1,3 @@ [beman_submodule] remote=https://github.com/bemanproject/infra.git -commit_hash=3dc3acd501eccaf3c9bd8d93edb1ebf1fa58d8c6 +commit_hash=ea3ef79f77fdcc378149ebc7406e81e9ceb04146 diff --git a/infra/.devcontainer/docker_dev_container/devcontainer.json b/infra/.devcontainer/docker_dev_container/devcontainer.json deleted file mode 100644 index 6707a25..0000000 --- a/infra/.devcontainer/docker_dev_container/devcontainer.json +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -{ - "name": "beman.infra Docker Devcontainer", - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "ghcr.io/devcontainers/features/docker-in-docker:2": { - "version": "latest", - "enableNonRootDocker": true, - "moby": false - }, - "ghcr.io/devcontainers-extra/features/pre-commit:2": {} - } -} diff --git a/infra/.github/CODEOWNERS b/infra/.github/CODEOWNERS index d3be6f1..4ff90a4 100644 --- a/infra/.github/CODEOWNERS +++ b/infra/.github/CODEOWNERS @@ -1 +1 @@ -* @bemanproject/core-reviewers +* @ednolan @neatudarius @rishyak @wusatosi @JeffGarland diff --git a/infra/.github/workflows/beman-submodule.yml b/infra/.github/workflows/beman-submodule.yml deleted file mode 100644 index d18dd7f..0000000 --- a/infra/.github/workflows/beman-submodule.yml +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -name: beman-submodule tests - -on: - push: - pull_request: - workflow_dispatch: - -jobs: - beman-submodule-script-ci: - name: beman_module.py ci - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.13 - - - name: Install pytest - run: | - python3 -m pip install pytest - - - name: Run pytest - run: | - cd tools/beman-submodule/ - pytest diff --git a/infra/.github/workflows/build_devcontainer.yml b/infra/.github/workflows/build_devcontainer.yml deleted file mode 100644 index 1f26fc7..0000000 --- a/infra/.github/workflows/build_devcontainer.yml +++ /dev/null @@ -1,97 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -name: Publish Beman Containers - -on: - push: - paths: - - ".github/workflows/build_devcontainer.yml" - - "containers/**" - workflow_dispatch: - -env: - REGISTRY: ghcr.io - DEBUG_NAME: ${{ github.repository }} - DEPLOY_DEV_NAME_PREFIX: bemanproject/devcontainers - DEPLOY_TESTING_NAME_PREFIX: bemanproject/testingcontainers - BASE_IMAGE_DEV: mcr.microsoft.com/devcontainers/cpp:1-ubuntu-24.04 - BASE_IMAGE_TEST: ubuntu:24.04 - -permissions: - packages: write - -jobs: - containers: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - compilers: - - kind: gcc - version: 14 - - kind: gcc - version: 13 - - kind: gcc - version: 12 - - kind: gcc - version: 11 - - kind: clang - version: 21 - - kind: clang - version: 20 - - kind: clang - version: 19 - - kind: clang - version: 18 - - kind: clang - version: 17 - usage: [dev, test] - name: "${{ matrix.usage }}: ${{ matrix.compilers.kind }}-${{ matrix.compilers.version }}" - steps: - - name: Compute Image Name - id: image_name - run: | - if [ "${{ github.repository }}/${{ github.ref }}" != "bemanproject/infra/refs/heads/main" ]; then - image_name="${{ env.DEBUG_NAME }}" - tag="${{ matrix.usage }}-${{ matrix.compilers.kind }}-${{ matrix.compilers.version }}" - else - if [ "${{ matrix.usage }}" = "dev" ]; then - image_name="${{ env.DEPLOY_DEV_NAME_PREFIX }}-${{ matrix.compilers.kind }}" - else - image_name="${{ env.DEPLOY_TESTING_NAME_PREFIX }}-${{ matrix.compilers.kind }}" - fi - tag="${{ matrix.compilers.version }}" - fi - - echo "Image Name: $image_name, Tag: $tag" - - echo "image_name=$image_name" >> "$GITHUB_OUTPUT" - echo "tag=$tag" >> "$GITHUB_OUTPUT" - - name: Compute Image base - id: image_base - run: | - if [ "${{ matrix.usage }}" == "dev" ]; then - echo "image=${{ env.BASE_IMAGE_DEV }}" >> "$GITHUB_OUTPUT" - else - echo "image=${{ env.BASE_IMAGE_TEST }}" >> "$GITHUB_OUTPUT" - fi - - name: Checkout repository - uses: actions/checkout@v4 - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Docker image - uses: docker/build-push-action@v6 - with: - context: containers - build-args: | - base_image=${{ steps.image_base.outputs.image }} - compiler_kind=${{ matrix.compilers.kind }} - compiler_version=${{ matrix.compilers.version }} - push: true - tags: ${{ env.REGISTRY }}/${{ steps.image_name.outputs.image_name }}:${{ steps.image_name.outputs.tag }} - # https://github.com/docker/build-push-action/issues/894 - provenance: false 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 index 259148f..b7cdbb5 100644 --- a/infra/.gitignore +++ b/infra/.gitignore @@ -30,3 +30,30 @@ *.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/.markdownlint.yaml b/infra/.markdownlint.yaml deleted file mode 100644 index 81f5fcd..0000000 --- a/infra/.markdownlint.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# MD033/no-inline-html : Inline HTML : https://github.com/DavidAnson/markdownlint/blob/v0.35.0/doc/md033.md -# Disable inline html linter is needed for
-MD033: false - -# MD013/line-length : Line length : https://github.com/DavidAnson/markdownlint/blob/v0.35.0/doc/md013.md -# Conforms to .clang-format ColumnLimit -# Update the comment in .clang-format if we no-longer tie these two column limits. -MD013: - line_length: 119 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/README.md b/infra/README.md index 4d31a49..bf9bbb0 100644 --- a/infra/README.md +++ b/infra/README.md @@ -2,10 +2,87 @@ -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. +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. -* `tools/`: Tools used to manage the infrastructure and the codebase (e.g., linting, formatting, etc.). + +## 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/infra/cmake/appleclang-toolchain.cmake b/infra/cmake/appleclang-toolchain.cmake index 5f44e80..70ef548 100644 --- a/infra/cmake/appleclang-toolchain.cmake +++ b/infra/cmake/appleclang-toolchain.cmake @@ -39,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/infra/cmake/gnu-toolchain.cmake b/infra/cmake/gnu-toolchain.cmake index b6dddf6..d3b9f92 100644 --- a/infra/cmake/gnu-toolchain.cmake +++ b/infra/cmake/gnu-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/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/infra/cmake/llvm-toolchain.cmake b/infra/cmake/llvm-toolchain.cmake index 5f5ee4b..f1623b7 100644 --- a/infra/cmake/llvm-toolchain.cmake +++ b/infra/cmake/llvm-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/msvc-toolchain.cmake b/infra/cmake/msvc-toolchain.cmake index c2fffa7..bdc24de 100644 --- a/infra/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 index 82c5db2..0564513 100644 --- a/infra/cmake/use-fetch-content.cmake +++ b/infra/cmake/use-fetch-content.cmake @@ -15,8 +15,7 @@ message(TRACE "BemanExemplar_projectDir=\"${BemanExemplar_projectDir}\"") message(TRACE "BEMAN_EXEMPLAR_LOCKFILE=\"${BEMAN_EXEMPLAR_LOCKFILE}\"") file( - REAL_PATH - "${BEMAN_EXEMPLAR_LOCKFILE}" + REAL_PATH "${BEMAN_EXEMPLAR_LOCKFILE}" BemanExemplar_lockfile BASE_DIRECTORY "${BemanExemplar_projectDir}" EXPAND_TILDE @@ -38,8 +37,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "dependencies" field and store it in BemanExemplar_dependenciesObj string( - JSON - BemanExemplar_dependenciesObj + JSON BemanExemplar_dependenciesObj ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_rootObj}" "dependencies" @@ -50,8 +48,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the length of the libraries array and store it in BemanExemplar_dependenciesObj string( - JSON - BemanExemplar_numDependencies + JSON BemanExemplar_numDependencies ERROR_VARIABLE BemanExemplar_error LENGTH "${BemanExemplar_dependenciesObj}" ) @@ -59,6 +56,10 @@ function(BemanExemplar_provideDependency method package_name) 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}") @@ -69,8 +70,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the dependency object at BemanExemplar_index # and store it in BemanExemplar_depObj string( - JSON - BemanExemplar_depObj + JSON BemanExemplar_depObj ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_dependenciesObj}" "${BemanExemplar_index}" @@ -84,8 +84,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "name" field and store it in BemanExemplar_name string( - JSON - BemanExemplar_name + JSON BemanExemplar_name ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_depObj}" "name" @@ -99,8 +98,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "package_name" field and store it in BemanExemplar_pkgName string( - JSON - BemanExemplar_pkgName + JSON BemanExemplar_pkgName ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_depObj}" "package_name" @@ -114,8 +112,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "git_repository" field and store it in BemanExemplar_repo string( - JSON - BemanExemplar_repo + JSON BemanExemplar_repo ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_depObj}" "git_repository" @@ -129,8 +126,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "git_tag" field and store it in BemanExemplar_tag string( - JSON - BemanExemplar_tag + JSON BemanExemplar_tag ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_depObj}" "git_tag" @@ -145,10 +141,13 @@ function(BemanExemplar_provideDependency method package_name) if(method STREQUAL "FIND_PACKAGE") if(package_name STREQUAL BemanExemplar_pkgName) string( - APPEND - BemanExemplar_debug + APPEND BemanExemplar_debug "Redirecting find_package calls for ${BemanExemplar_pkgName} " - "to FetchContent logic fetching ${BemanExemplar_repo} at " + "to FetchContent logic.\n" + ) + string( + APPEND BemanExemplar_debug + "Fetching ${BemanExemplar_repo} at " "${BemanExemplar_tag} according to ${BemanExemplar_lockfile}." ) message(DEBUG "${BemanExemplar_debug}") @@ -158,9 +157,63 @@ function(BemanExemplar_provideDependency method package_name) GIT_TAG "${BemanExemplar_tag}" EXCLUDE_FROM_ALL ) - set(INSTALL_GTEST OFF) # Disable GoogleTest installation + + # 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) @@ -173,3 +226,6 @@ 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/infra/containers/Dockerfile b/infra/containers/Dockerfile deleted file mode 100644 index f5f5f9d..0000000 --- a/infra/containers/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -ARG base_image=mcr.microsoft.com/devcontainers/cpp:1-ubuntu-24.04 -FROM ${base_image} - -# Create the vscode user -RUN bash </dev/null; then - apt-get update && apt-get install -y sudo adduser - useradd -ms /bin/bash -p "" vscode && usermod -aG sudo vscode - fi -EOF - -USER vscode -WORKDIR /tmp - -COPY install_sys.sh . -RUN bash install_sys.sh - -# Newer gcc/ clang is needed to avoid ASAN Stalling, which is turned on by default across beman projects. -# See: https://github.com/google/sanitizers/issues/1614 -# Minimal version: clang-18.1.3, gcc-13.2 -ARG compiler_kind=gcc -ARG compiler_version=14 - -COPY install_compiler.sh . -RUN bash install_compiler.sh ${compiler_kind} ${compiler_version} - -# Common dependency: google-test -RUN sudo apt-get install -y libgtest-dev - -# Pre-commit is beman library's standard linting tool -RUN sudo apt-get install -y pipx -RUN pipx install pre-commit -ENV PATH="/home/vscode/.local/bin:${PATH}" - -ENTRYPOINT ["/usr/bin/bash"] diff --git a/infra/containers/README.md b/infra/containers/README.md deleted file mode 100644 index 83b357d..0000000 --- a/infra/containers/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Containers - - - -This folder contains the infrastructure for Beman project's -generic container images. You can checkout available images in beman's -[GitHub Packages page](https://github.com/orgs/bemanproject/packages). - -These images includes: - -- The latest CMake from kitware's apt repository -- Latest compiler based on build args (gcc or clang) installed from the universe repository -- [pre-commit](https://pre-commit.com/), the standard linter manager across Beman - -## Devcontainer - -The image is build on top of GitHub's -[C++ devcontainer image](https://github.com/devcontainers/images/tree/main/src/cpp) -for Ubuntu 24.04. - -### Example devcontainer setup - -```json -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -{ - "name": "Beman Generic Devcontainer", - "image": "ghcr.io/bemanproject/devcontainers-gcc:14", - "postCreateCommand": "pre-commit", - "customizations": { - "vscode": { - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools" - ] - } - } -} -``` - -### Building your own image - -You can build your own Beman devcontainer image with: - -```bash -docker build devcontainer/ -``` diff --git a/infra/containers/install_compiler.sh b/infra/containers/install_compiler.sh deleted file mode 100644 index bef0e1c..0000000 --- a/infra/containers/install_compiler.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -set -e -set +x -TOOL=$1 -VERSION=$2 - -echo "Install ${TOOL} at: ${VERSION}" - -shopt -s nocasematch -if [ "$TOOL" = "gcc" ]; then - sudo apt-get remove -y gcc-"$VERSION" g++-"$VERSION" gcc g++ - sudo apt-get install -y gcc-"$VERSION" g++-"$VERSION" lcov - - sudo rm -f /usr/bin/gcc - sudo rm -f /usr/bin/g++ - sudo rm -f /usr/bin/gcov - - sudo ln -s "$(which gcc-"$VERSION")" /usr/bin/gcc - sudo ln -s "$(which g++-"$VERSION")" /usr/bin/g++ - sudo ln -s "$(which gcov-"$VERSION")" /usr/bin/gcov - - gcc --version -else - sudo apt-get install -y lsb-release wget software-properties-common gnupg - wget https://apt.llvm.org/llvm.sh - - sudo bash llvm.sh "${VERSION}" - sudo apt-get install -y libc++-"$VERSION"-dev clang-tools-"$VERSION" lcov - - sudo rm -f /usr/bin/clang - sudo rm -f /usr/bin/clang++ - - sudo ln -s "$(which clang-"$VERSION")" /usr/bin/clang - sudo ln -s "$(which clang++-"$VERSION")" /usr/bin/clang++ - - clang --version -fi diff --git a/infra/containers/install_sys.sh b/infra/containers/install_sys.sh deleted file mode 100644 index 0e31aca..0000000 --- a/infra/containers/install_sys.sh +++ /dev/null @@ -1,10 +0,0 @@ -# Install Basic utilities -sudo apt-get install -y ca-certificates gpg wget git curl - -# Install Latest CMake -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 -echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ noble main' | sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null -sudo apt-get update && sudo apt-get install -y cmake - -# Install Ninja -sudo apt-get install -y ninja-build diff --git a/infra/tools/beman-submodule/README.md b/infra/tools/beman-submodule/README.md deleted file mode 100644 index 36883ad..0000000 --- a/infra/tools/beman-submodule/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# beman-submodule - - - -## What is this script? - -`beman-submodule` provides some of the features of `git submodule`, adding child git -repositories to a parent git repository, but unlike with `git submodule`, the entire child -repo is directly checked in, so only maintainers, not users, need to run this script. The -command line interface mimics `git submodule`'s. - -## How do I add a beman submodule to my repository? - -The first beman submodule you should add is this repository, `infra/`, which you can -bootstrap by running: - - -```sh -curl -s https://raw.githubusercontent.com/bemanproject/infra/refs/heads/main/tools/beman-submodule/beman-submodule | python3 - add https://github.com/bemanproject/infra.git -``` - -Once that's added, you can run the script from `infra/tools/beman-submodule/beman-submodule`. - -## How do I update a beman submodule to the latest trunk? - -You can run `beman-submodule update --remote` to update all beman submodule to latest -trunk, or e.g. `beman-submodule update --remote infra` to update only a specific one. - -## How does it work under the hood? - -Along with the files from the child repository, it creates a dotfile called -`.beman_submodule`, which looks like this: - -```ini -[beman_submodule] -remote=https://github.com/bemanproject/infra.git -commit_hash=9b88395a86c4290794e503e94d8213b6c442ae77 -``` - -## How do I update a beman submodule to a specific commit or change the remote URL? - -You can edit the corresponding lines in the `.beman_submodule` file and run -`beman-submodule update` to update the state of the beman submodule to the new -`.beman_submodule` settings. - -## How can I make CI ensure that my beman submodules are in a valid state? - -Add this job to your CI workflow: - -```yaml - beman-submodule-test: - runs-on: ubuntu-latest - name: "Check beman submodules for consistency" - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: beman submodule consistency check - run: | - (set -o pipefail; ./infra/tools/beman-submodule/beman-submodule status | grep -qvF '+') -``` - -This will fail if the contents of any beman submodule don't match what's specified in the -`.beman_submodule` file. diff --git a/infra/tools/beman-submodule/beman-submodule b/infra/tools/beman-submodule/beman-submodule deleted file mode 100755 index 2007fc4..0000000 --- a/infra/tools/beman-submodule/beman-submodule +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python3 - -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -import argparse -import configparser -import filecmp -import os -import pathlib -import shutil -import subprocess -import sys -import tempfile - -def directory_compare(dir1, dir2, ignore): - compared = filecmp.dircmp(dir1, dir2, ignore=ignore) - if compared.left_only or compared.right_only or compared.diff_files: - return False - for common_dir in compared.common_dirs: - path1 = os.path.join(dir1, common_dir) - path2 = os.path.join(dir2, common_dir) - if not directory_compare(path1, path2, ignore): - return False - return True - -class BemanSubmodule: - def __init__(self, dirpath, remote, commit_hash): - self.dirpath = dirpath - self.remote = remote - self.commit_hash = commit_hash - -def parse_beman_submodule_file(path): - config = configparser.ConfigParser() - read_result = config.read(path) - def fail(): - raise Exception(f'Failed to parse {path} as a .beman_submodule file') - if not read_result: - fail() - if not 'beman_submodule' in config: - fail() - if not 'remote' in config['beman_submodule']: - fail() - if not 'commit_hash' in config['beman_submodule']: - fail() - return BemanSubmodule( - str(pathlib.Path(path).resolve().parent), - config['beman_submodule']['remote'], config['beman_submodule']['commit_hash']) - -def get_beman_submodule(dir): - beman_submodule_filepath = os.path.join(dir, '.beman_submodule') - if os.path.isfile(beman_submodule_filepath): - return parse_beman_submodule_file(beman_submodule_filepath) - else: - return None - -def find_beman_submodules_in(dir): - assert os.path.isdir(dir) - result = [] - for dirpath, _, filenames in os.walk(dir): - if '.beman_submodule' in filenames: - result.append(parse_beman_submodule_file(os.path.join(dirpath, '.beman_submodule'))) - return sorted(result, key=lambda module: module.dirpath) - -def cwd_git_repository_path(): - process = subprocess.run( - ['git', 'rev-parse', '--show-toplevel'], capture_output=True, text=True, - check=False) - if process.returncode == 0: - return process.stdout.strip() - elif "fatal: not a git repository" in process.stderr: - return None - else: - raise Exception("git rev-parse --show-toplevel failed") - -def clone_beman_submodule_into_tmpdir(beman_submodule, remote): - tmpdir = tempfile.TemporaryDirectory() - subprocess.run( - ['git', 'clone', beman_submodule.remote, tmpdir.name], capture_output=True, - check=True) - if not remote: - subprocess.run( - ['git', '-C', tmpdir.name, 'reset', '--hard', beman_submodule.commit_hash], - capture_output=True, check=True) - return tmpdir - -def beman_submodule_status(beman_submodule): - tmpdir = clone_beman_submodule_into_tmpdir(beman_submodule, False) - if directory_compare(tmpdir.name, beman_submodule.dirpath, ['.beman_submodule', '.git']): - status_character=' ' - else: - status_character='+' - parent_repo_path = cwd_git_repository_path() - if not parent_repo_path: - raise Exception('this is not a git repository') - relpath = pathlib.Path( - beman_submodule.dirpath).relative_to(pathlib.Path(parent_repo_path)) - return status_character + ' ' + beman_submodule.commit_hash + ' ' + str(relpath) - -def beman_submodule_update(beman_submodule, remote): - tmpdir = clone_beman_submodule_into_tmpdir(beman_submodule, remote) - shutil.rmtree(beman_submodule.dirpath) - with open(os.path.join(tmpdir.name, '.beman_submodule'), 'w') as f: - f.write('[beman_submodule]\n') - f.write(f'remote={beman_submodule.remote}\n') - f.write(f'commit_hash={beman_submodule.commit_hash}\n') - shutil.rmtree(os.path.join(tmpdir.name, '.git')) - shutil.copytree(tmpdir.name, beman_submodule.dirpath) - -def update_command(remote, path): - if not path: - parent_repo_path = cwd_git_repository_path() - if not parent_repo_path: - raise Exception('this is not a git repository') - beman_submodules = find_beman_submodules_in(parent_repo_path) - else: - beman_submodule = get_beman_submodule(path) - if not beman_submodule: - raise Exception(f'{path} is not a beman_submodule') - beman_submodules = [beman_submodule] - for beman_submodule in beman_submodules: - beman_submodule_update(beman_submodule, remote) - -def add_command(repository, path): - tmpdir = tempfile.TemporaryDirectory() - subprocess.run( - ['git', 'clone', repository], capture_output=True, check=True, cwd=tmpdir.name) - repository_name = os.listdir(tmpdir.name)[0] - if not path: - path = repository_name - if os.path.exists(path): - raise Exception(f'{path} exists') - os.makedirs(path) - tmpdir_repo = os.path.join(tmpdir.name, repository_name) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir_repo) - with open(os.path.join(tmpdir_repo, '.beman_submodule'), 'w') as f: - f.write('[beman_submodule]\n') - f.write(f'remote={repository}\n') - f.write(f'commit_hash={sha_process.stdout.strip()}\n') - shutil.rmtree(os.path.join(tmpdir_repo, '.git')) - shutil.copytree(tmpdir_repo, path, dirs_exist_ok=True) - -def status_command(paths): - if not paths: - parent_repo_path = cwd_git_repository_path() - if not parent_repo_path: - raise Exception('this is not a git repository') - beman_submodules = find_beman_submodules_in(parent_repo_path) - else: - beman_submodules = [] - for path in paths: - beman_submodule = get_beman_submodule(path) - if not beman_submodule: - raise Exception(f'{path} is not a beman_submodule') - beman_submodules.append(beman_submodule) - for beman_submodule in beman_submodules: - print(beman_submodule_status(beman_submodule)) - -def get_parser(): - parser = argparse.ArgumentParser(description='Beman pseudo-submodule tool') - subparsers = parser.add_subparsers(dest='command', help='available commands') - parser_update = subparsers.add_parser('update', help='update beman_submodules') - parser_update.add_argument( - '--remote', action='store_true', - help='update a beman_submodule to its latest from upstream') - parser_update.add_argument( - 'beman_submodule_path', nargs='?', - help='relative path to the beman_submodule to update') - parser_add = subparsers.add_parser('add', help='add a new beman_submodule') - parser_add.add_argument('repository', help='git repository to add') - parser_add.add_argument( - 'path', nargs='?', help='path where the repository will be added') - parser_status = subparsers.add_parser( - 'status', help='show the status of beman_submodules') - parser_status.add_argument('paths', nargs='*') - return parser - -def parse_args(args): - return get_parser().parse_args(args); - -def usage(): - return get_parser().format_help() - -def run_command(args): - if args.command == 'update': - update_command(args.remote, args.beman_submodule_path) - elif args.command == 'add': - add_command(args.repository, args.path) - elif args.command == 'status': - status_command(args.paths) - else: - raise Exception(usage()) - -def check_for_git(path): - env = os.environ.copy() - if path is not None: - env["PATH"] = path - return shutil.which("git", path=env.get("PATH")) is not None - -def main(): - try: - if not check_for_git(None): - raise Exception('git not found in PATH') - args = parse_args(sys.argv[1:]) - run_command(args) - except Exception as e: - print("Error:", e, file=sys.stderr) - sys.exit(1) - -if __name__ == '__main__': - main() diff --git a/infra/tools/beman-submodule/test/test_beman_submodule.py b/infra/tools/beman-submodule/test/test_beman_submodule.py deleted file mode 100644 index 47e2303..0000000 --- a/infra/tools/beman-submodule/test/test_beman_submodule.py +++ /dev/null @@ -1,359 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -import os -import pathlib -import pytest -import shutil -import stat -import subprocess -import tempfile - -# https://stackoverflow.com/a/19011259 -import types -import importlib.machinery -loader = importlib.machinery.SourceFileLoader( - 'beman_submodule', - os.path.join(os.path.dirname(os.path.realpath(__file__)), '../beman-submodule')) -beman_submodule = types.ModuleType(loader.name) -loader.exec_module(beman_submodule) - -def create_test_git_repository(): - tmpdir = tempfile.TemporaryDirectory() - subprocess.run(['git', 'init'], check=True, cwd=tmpdir.name, capture_output=True) - def make_commit(a_txt_contents): - with open(os.path.join(tmpdir.name, 'a.txt'), 'w') as f: - f.write(a_txt_contents) - subprocess.run( - ['git', 'add', 'a.txt'], check=True, cwd=tmpdir.name, capture_output=True) - subprocess.run( - ['git', '-c', 'user.name=test', '-c', 'user.email=test@example.com', 'commit', - '--author="test "', '-m', 'test'], - check=True, cwd=tmpdir.name, capture_output=True) - make_commit('A') - make_commit('a') - return tmpdir - -def test_directory_compare(): - def create_dir_structure(dir_path): - bar_path = os.path.join(dir_path, 'bar') - os.makedirs(bar_path) - - with open(os.path.join(dir_path, 'foo.txt'), 'w') as f: - f.write('foo') - with open(os.path.join(bar_path, 'baz.txt'), 'w') as f: - f.write('baz') - - with tempfile.TemporaryDirectory() as dir_a, \ - tempfile.TemporaryDirectory() as dir_b: - - create_dir_structure(dir_a) - create_dir_structure(dir_b) - - assert beman_submodule.directory_compare(dir_a, dir_b, []) - - with open(os.path.join(os.path.join(dir_a, 'bar'), 'quux.txt'), 'w') as f: - f.write('quux') - - assert not beman_submodule.directory_compare(dir_a, dir_b, []) - assert beman_submodule.directory_compare(dir_a, dir_b, ['quux.txt']) - -def test_parse_beman_submodule_file(): - def valid_file(): - tmpfile = tempfile.NamedTemporaryFile() - tmpfile.write('[beman_submodule]\n'.encode('utf-8')) - tmpfile.write( - 'remote=git@github.com:bemanproject/infra.git\n'.encode('utf-8')) - tmpfile.write( - 'commit_hash=9b88395a86c4290794e503e94d8213b6c442ae77\n'.encode('utf-8')) - tmpfile.flush() - module = beman_submodule.parse_beman_submodule_file(tmpfile.name) - assert module.dirpath == str(pathlib.Path(tmpfile.name).resolve().parent) - assert module.remote == 'git@github.com:bemanproject/infra.git' - assert module.commit_hash == '9b88395a86c4290794e503e94d8213b6c442ae77' - valid_file() - def invalid_file_missing_remote(): - threw = False - try: - tmpfile = tempfile.NamedTemporaryFile() - tmpfile.write('[beman_submodule]\n'.encode('utf-8')) - tmpfile.write( - 'commit_hash=9b88395a86c4290794e503e94d8213b6c442ae77\n'.encode('utf-8')) - tmpfile.flush() - beman_submodule.parse_beman_submodule_file(tmpfile.name) - except: - threw = True - assert threw - invalid_file_missing_remote() - def invalid_file_missing_commit_hash(): - threw = False - try: - tmpfile = tempfile.NamedTemporaryFile() - tmpfile.write('[beman_submodule]\n'.encode('utf-8')) - tmpfile.write( - 'remote=git@github.com:bemanproject/infra.git\n'.encode('utf-8')) - tmpfile.flush() - beman_submodule.parse_beman_submodule_file(tmpfile.name) - except: - threw = True - assert threw - invalid_file_missing_commit_hash() - def invalid_file_wrong_section(): - threw = False - try: - tmpfile = tempfile.NamedTemporaryFile() - tmpfile.write('[invalid]\n'.encode('utf-8')) - tmpfile.write( - 'remote=git@github.com:bemanproject/infra.git\n'.encode('utf-8')) - tmpfile.write( - 'commit_hash=9b88395a86c4290794e503e94d8213b6c442ae77\n'.encode('utf-8')) - tmpfile.flush() - beman_submodule.parse_beman_submodule_file(tmpfile.name) - except: - threw = True - assert threw - invalid_file_wrong_section() - -def test_get_beman_submodule(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo') - assert beman_submodule.get_beman_submodule('foo') - os.remove('foo/.beman_submodule') - assert not beman_submodule.get_beman_submodule('foo') - os.chdir(original_cwd) - -def test_find_beman_submodules_in(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo') - beman_submodule.add_command(tmpdir.name, 'bar') - beman_submodules = beman_submodule.find_beman_submodules_in(tmpdir2.name) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - assert beman_submodules[0].dirpath == os.path.join(tmpdir2.name, 'bar') - assert beman_submodules[0].remote == tmpdir.name - assert beman_submodules[0].commit_hash == sha - assert beman_submodules[1].dirpath == os.path.join(tmpdir2.name, 'foo') - assert beman_submodules[1].remote == tmpdir.name - assert beman_submodules[1].commit_hash == sha - os.chdir(original_cwd) - -def test_cwd_git_repository_path(): - original_cwd = os.getcwd() - tmpdir = tempfile.TemporaryDirectory() - os.chdir(tmpdir.name) - assert not beman_submodule.cwd_git_repository_path() - subprocess.run(['git', 'init']) - assert beman_submodule.cwd_git_repository_path() == tmpdir.name - os.chdir(original_cwd) - -def test_clone_beman_submodule_into_tmpdir(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - beman_submodule.add_command(tmpdir.name, 'foo') - module = beman_submodule.get_beman_submodule(os.path.join(tmpdir2.name, 'foo')) - module.commit_hash = sha - tmpdir3 = beman_submodule.clone_beman_submodule_into_tmpdir(module, False) - assert not beman_submodule.directory_compare(tmpdir.name, tmpdir3.name, ['.git']) - tmpdir4 = beman_submodule.clone_beman_submodule_into_tmpdir(module, True) - assert beman_submodule.directory_compare(tmpdir.name, tmpdir4.name, ['.git']) - subprocess.run( - ['git', 'reset', '--hard', sha], capture_output=True, check=True, - cwd=tmpdir.name) - assert beman_submodule.directory_compare(tmpdir.name, tmpdir3.name, ['.git']) - os.chdir(original_cwd) - -def test_beman_submodule_status(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo') - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - assert ' ' + sha + ' foo' == beman_submodule.beman_submodule_status( - beman_submodule.get_beman_submodule(os.path.join(tmpdir2.name, 'foo'))) - with open(os.path.join(os.path.join(tmpdir2.name, 'foo'), 'a.txt'), 'w') as f: - f.write('b') - assert '+ ' + sha + ' foo' == beman_submodule.beman_submodule_status( - beman_submodule.get_beman_submodule(os.path.join(tmpdir2.name, 'foo'))) - os.chdir(original_cwd) - -def test_update_command_no_paths(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo') - beman_submodule.add_command(tmpdir.name, 'bar') - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - subprocess.run( - ['git', 'reset', '--hard', sha], capture_output=True, check=True, - cwd=tmpdir.name) - with open(os.path.join(os.path.join(tmpdir2.name, 'foo'), '.beman_submodule'), 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={sha}\n') - with open(os.path.join(os.path.join(tmpdir2.name, 'bar'), '.beman_submodule'), 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={sha}\n') - beman_submodule.update_command(tmpdir.name, None) - assert beman_submodule.directory_compare( - tmpdir.name, os.path.join(tmpdir2.name, 'foo'), ['.git', '.beman_submodule']) - assert beman_submodule.directory_compare( - tmpdir.name, os.path.join(tmpdir2.name, 'bar'), ['.git', '.beman_submodule']) - os.chdir(original_cwd) - -def test_update_command_with_path(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - tmpdir_copy1 = tempfile.TemporaryDirectory() - shutil.copytree(tmpdir.name, tmpdir_copy1.name, dirs_exist_ok=True) - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo') - beman_submodule.add_command(tmpdir.name, 'bar') - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - subprocess.run( - ['git', 'reset', '--hard', sha], capture_output=True, check=True, - cwd=tmpdir.name) - with open(os.path.join(os.path.join(tmpdir2.name, 'foo'), '.beman_submodule'), 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={sha}\n') - with open(os.path.join(os.path.join(tmpdir2.name, 'bar'), '.beman_submodule'), 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={sha}\n') - beman_submodule.update_command(tmpdir.name, 'foo') - assert beman_submodule.directory_compare( - tmpdir.name, os.path.join(tmpdir2.name, 'foo'), ['.git', '.beman_submodule']) - assert beman_submodule.directory_compare( - tmpdir_copy1.name, os.path.join(tmpdir2.name, 'bar'), ['.git', '.beman_submodule']) - os.chdir(original_cwd) - -def test_add_command(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo') - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - assert beman_submodule.directory_compare( - tmpdir.name, os.path.join(tmpdir2.name, 'foo'), ['.git', '.beman_submodule']) - with open(os.path.join(os.path.join(tmpdir2.name, 'foo'), '.beman_submodule'), 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={sha}\n' - os.chdir(original_cwd) - -def test_status_command_no_paths(capsys): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo') - beman_submodule.add_command(tmpdir.name, 'bar') - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - with open(os.path.join(os.path.join(tmpdir2.name, 'bar'), 'a.txt'), 'w') as f: - f.write('b') - beman_submodule.status_command([]) - sha = sha_process.stdout.strip() - assert capsys.readouterr().out == '+ ' + sha + ' bar\n' + ' ' + sha + ' foo\n' - os.chdir(original_cwd) - -def test_status_command_with_path(capsys): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = os.getcwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo') - beman_submodule.add_command(tmpdir.name, 'bar') - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - with open(os.path.join(os.path.join(tmpdir2.name, 'bar'), 'a.txt'), 'w') as f: - f.write('b') - beman_submodule.status_command(['bar']) - sha = sha_process.stdout.strip() - assert capsys.readouterr().out == '+ ' + sha + ' bar\n' - os.chdir(original_cwd) - -def test_check_for_git(): - tmpdir = tempfile.TemporaryDirectory() - assert not beman_submodule.check_for_git(tmpdir.name) - fake_git_path = os.path.join(tmpdir.name, 'git') - with open(fake_git_path, 'w'): - pass - os.chmod(fake_git_path, stat.S_IRWXU) - assert beman_submodule.check_for_git(tmpdir.name) - -def test_parse_args(): - def plain_update(): - args = beman_submodule.parse_args(['update']) - assert args.command == 'update' - assert not args.remote - assert not args.beman_submodule_path - plain_update() - def update_remote(): - args = beman_submodule.parse_args(['update', '--remote']) - assert args.command == 'update' - assert args.remote - assert not args.beman_submodule_path - update_remote() - def update_path(): - args = beman_submodule.parse_args(['update', 'infra/']) - assert args.command == 'update' - assert not args.remote - assert args.beman_submodule_path == 'infra/' - update_path() - def update_path_remote(): - args = beman_submodule.parse_args(['update', '--remote', 'infra/']) - assert args.command == 'update' - assert args.remote - assert args.beman_submodule_path == 'infra/' - update_path_remote() - def plain_add(): - args = beman_submodule.parse_args(['add', 'git@github.com:bemanproject/infra.git']) - assert args.command == 'add' - assert args.repository == 'git@github.com:bemanproject/infra.git' - assert not args.path - plain_add() - def add_path(): - args = beman_submodule.parse_args( - ['add', 'git@github.com:bemanproject/infra.git', 'infra/']) - assert args.command == 'add' - assert args.repository == 'git@github.com:bemanproject/infra.git' - assert args.path == 'infra/' - add_path() - def plain_status(): - args = beman_submodule.parse_args(['status']) - assert args.command == 'status' - assert args.paths == [] - plain_status() - def status_one_module(): - args = beman_submodule.parse_args(['status', 'infra/']) - assert args.command == 'status' - assert args.paths == ['infra/'] - status_one_module() - def status_multiple_modules(): - args = beman_submodule.parse_args(['status', 'infra/', 'foobar/']) - assert args.command == 'status' - assert args.paths == ['infra/', 'foobar/'] - status_multiple_modules() diff --git a/lockfile.json b/lockfile.json index 3a69ab1..787b905 100644 --- a/lockfile.json +++ b/lockfile.json @@ -4,7 +4,10 @@ "name": "googletest", "package_name": "GTest", "git_repository": "https://github.com/google/googletest.git", - "git_tag": "6910c9d9165801d8827d628cb72eb7ea9dd538c5" + "git_tag": "6910c9d9165801d8827d628cb72eb7ea9dd538c5", + "cmake_args": { + "INSTALL_GTEST": "OFF" + } } ] } diff --git a/src/beman/copyable_function/CMakeLists.txt b/src/beman/copyable_function/CMakeLists.txt deleted file mode 100644 index aa5dd2a..0000000 --- a/src/beman/copyable_function/CMakeLists.txt +++ /dev/null @@ -1,62 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -add_library(beman.copyable_function) -add_library(beman::copyable_function ALIAS beman.copyable_function) - -target_sources(beman.copyable_function PRIVATE copyable_function.cpp) - -target_sources( - beman.copyable_function - PUBLIC - FILE_SET HEADERS - BASE_DIRS ${PROJECT_SOURCE_DIR}/include - FILES - ${PROJECT_SOURCE_DIR}/include/beman/copyable_function/copyable_function.hpp -) - -set_target_properties( - beman.copyable_function - PROPERTIES VERIFY_INTERFACE_HEADER_SETS ON EXPORT_NAME copyable_function -) - -install( - TARGETS beman.copyable_function - EXPORT beman.copyable_function - DESTINATION - ${CMAKE_INSTALL_LIBDIR}$<$:/debug> - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}$<$:/debug> - FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) - -if(BEMAN_COPYABLE_FUNCTION_INSTALL_CONFIG_FILE_PACKAGE) - include(CMakePackageConfigHelpers) - - configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake.in" - "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" - INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" - PATH_VARS PROJECT_NAME PROJECT_VERSION - ) - - write_basic_package_version_file( - "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion - ) - - install( - FILES - "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" - "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-version.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" - COMPONENT development - ) - - install( - EXPORT beman.copyable_function - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" - NAMESPACE beman:: - FILE ${PROJECT_NAME}-targets.cmake - COMPONENT development - ) -endif() diff --git a/src/beman/copyable_function/copyable_function.cpp b/src/beman/copyable_function/copyable_function.cpp deleted file mode 100644 index 8d1f5c5..0000000 --- a/src/beman/copyable_function/copyable_function.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include diff --git a/tests/beman/copyable_function/CMakeLists.txt b/tests/beman/copyable_function/CMakeLists.txt index 1ba5e1d..35966d2 100644 --- a/tests/beman/copyable_function/CMakeLists.txt +++ b/tests/beman/copyable_function/CMakeLists.txt @@ -1,16 +1,16 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -include(GoogleTest) +find_package(GTest REQUIRED) add_executable(beman.copyable_function.tests.copyable_function) target_sources( beman.copyable_function.tests.copyable_function - PRIVATE assignment.test.cpp ctor.test.cpp call.test.cpp swap.test.cpp + PRIVATE assignment.test.cpp call.test.cpp ctor.test.cpp swap.test.cpp ) - target_link_libraries( beman.copyable_function.tests.copyable_function PRIVATE beman::copyable_function GTest::gtest GTest::gtest_main ) -gtest_add_tests(beman.copyable_function.tests.copyable_function "" AUTO) +include(GoogleTest) +gtest_discover_tests(beman.copyable_function.tests.copyable_function)