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/.gitattributes b/.gitattributes new file mode 100644 index 0000000..793dce7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +infra/** linguist-vendored +cookiecutter/** linguist-vendored +*.bib -linguist-detectable +*.tex -linguist-detectable +papers/* linguist-documentation diff --git a/.github/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 21454e5..a7d8233 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -4,253 +4,129 @@ name: Continuous Integration Tests on: push: + branches: + - main pull_request: workflow_dispatch: schedule: - - cron: '30 15 * * *' + - cron: '6 13 * * 5' 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.3.0 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_CSTRING_VIEW_BUILD_TESTS=OFF" - - name: "Disable example building" - arg: "-DBEMAN_CSTRING_VIEW_BUILD_EXAMPLES=OFF" - - name: "Disable config-file package creation" - arg: "-DBEMAN_CSTRING_VIEW_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.3.0 + 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.3.0 + with: + matrix_config: > + { + "gcc": [ + { "versions": ["15"], + "tests": [ + { "cxxversions": ["c++26"], + "tests": [ + { "stdlibs": ["libstdc++"], + "tests": [ + "Debug.Default", "Release.Default", "Release.TSan", + "Release.MaxSan", "Debug.Werror", + "Debug.Coverage" + ] + } + ] + }, + { "cxxversions": ["c++23", "c++20"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + }, + { "versions": ["14"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [{ "stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "clang": [ + { "versions": ["22"], + "tests": [ + {"cxxversions": ["c++26"], + "tests": [ + { "stdlibs": ["libstdc++", "libc++"], + "tests": [ + "Debug.Default", "Release.Default", "Release.TSan", + "Release.MaxSan", "Debug.Werror" + ] + } + ] + }, + { "cxxversions": ["c++23", "c++20"], + "tests": [ + {"stdlibs": ["libstdc++", "libc++"], "tests": ["Release.Default"]} + ] + } + ] + }, + { "versions": ["21", "20", "19"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [ + {"stdlibs": ["libstdc++", "libc++"], "tests": ["Release.Default"]} + ] + } + ] + }, + { "versions": ["18", "17"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [{"stdlibs": ["libc++"], "tests": ["Release.Default"]}] + }, + { "cxxversions": ["c++20"], + "tests": [{"stdlibs": ["libstdc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "appleclang": [ + { "versions": ["latest"], + "tests": [ + { "cxxversions": ["c++26", "c++23", "c++20"], + "tests": [{ "stdlibs": ["libc++"], "tests": ["Release.Default"]}] + } + ] + } + ], + "msvc": [ + { "versions": ["latest"], + "tests": [ + { "cxxversions": ["c++23"], + "tests": [ + { "stdlibs": ["stl"], + "tests": ["Debug.Default", "Release.Default", "Release.MaxSan"] + } + ] + } + ] + } + ] + } create-issue-when-fault: - runs-on: ubuntu-latest - needs: [preset-test, gtest-test, configuration-test, compiler-test] + needs: [preset-test, build-and-test] if: failure() && github.event_name == 'schedule' - steps: - # See https://github.com/cli/cli/issues/5075 - - uses: actions/checkout@v4 - - name: Create issue - run: | - issue_num=$(gh issue list -s open -S "[SCHEDULED-BUILD] Build & Test failure" -L 1 --json number | jq 'if length == 0 then -1 else .[0].number end') - - body="**Build-and-Test Failure Report** - - **Time of Failure**: $(date -u '+%B %d, %Y, %H:%M %Z') - - **Commit**: [${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) - - **Action Run**: [View logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - The scheduled build-and-test triggered by cron has failed. - Please investigate the logs and recent changes associated with this commit or rerun the workflow if you believe this is an error." - - if [[ $issue_num -eq -1 ]]; then - gh issue create --repo ${{ github.repository }} --title "[SCHEDULED-BUILD] Build & Test failure" --body "$body" - else - gh issue comment --repo ${{ github.repository }} $issue_num --body "$body" - fi - env: - GH_TOKEN: ${{ github.token }} + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.3.0 diff --git a/.github/workflows/pre-commit-check.yml b/.github/workflows/pre-commit-check.yml new file mode 100644 index 0000000..2f91103 --- /dev/null +++ b/.github/workflows/pre-commit-check.yml @@ -0,0 +1,13 @@ +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 + +jobs: + pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.3.0 diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml new file mode 100644 index 0000000..7fc5ac5 --- /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: "39 15 * * 6" + +jobs: + auto-update-pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.3.0 + secrets: + APP_ID: ${{ secrets.AUTO_PR_BOT_APP_ID }} + PRIVATE_KEY: ${{ secrets.AUTO_PR_BOT_PRIVATE_KEY }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml deleted file mode 100644 index f3c4332..0000000 --- a/.github/workflows/pre-commit.yml +++ /dev/null @@ -1,76 +0,0 @@ -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: - -jobs: - pre-commit-push: - name: Pre-Commit check on Push - runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.13 - - # We wish to run pre-commit on all files instead of the changes - # only made in the push commit. - # - # So linting error persists when there's formatting problem. - - uses: pre-commit/action@v3.0.1 - - pre-commit-pr: - name: Pre-Commit check on PR - runs-on: ubuntu-latest - if: ${{ github.event_name == 'pull_request_target' }} - - permissions: - contents: read - checks: write - issues: write - pull-requests: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # pull_request_target checkout the base of the repo - # We need to checkout the actual pr to lint the changes. - - name: Checkout pr - run: gh pr checkout ${{ github.event.number }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.13 - - # we only lint on the changed file in PR. - - name: Get Changed Files - id: changed-files - uses: tj-actions/changed-files@v45 - - # See: - # https://github.com/tj-actions/changed-files?tab=readme-ov-file#using-local-git-directory- - - uses: pre-commit/action@v3.0.1 - id: run-pre-commit - with: - extra_args: --files ${{ steps.changed-files.outputs.all_changed_files }} - - # Review dog posts the suggested change from pre-commit to the pr. - - name: suggester / pre-commit - uses: reviewdog/action-suggester@v1 - if: ${{ failure() && steps.run-pre-commit.conclusion == 'failure' }} - with: - tool_name: pre-commit - level: warning - reviewdog_flags: "-fail-level=error" 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 8fd4a37..da9365a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,27 +13,29 @@ 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: v21.1.2 + rev: v22.1.1 hooks: - id: clang-format types_or: [c++, c] # CMake linting and formatting - repo: https://github.com/BlankSpruce/gersemi - rev: 0.22.3 + rev: 0.26.1 hooks: - id: gersemi name: CMake linting + exclude: ^.*/tests/.*/data/ # Exclude test data directories # Markdown linting # Config file: .markdownlint.yaml - - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.45.0 - hooks: - - id: markdownlint + # Commented out to disable this by default. Uncomment to enable markdown linting. + # - repo: https://github.com/igorshubovych/markdownlint-cli + # rev: v0.42.0 + # hooks: + # - id: markdownlint - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell diff --git a/CMakeLists.txt b/CMakeLists.txt index e69a90f..0fc4c44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,13 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -cmake_minimum_required(VERSION 3.25) - -set(CMAKE_CXX_STANDARD 23) +cmake_minimum_required(VERSION 3.30...4.3) project( beman.cstring_view # CMake Project Name, which is also the name of the top-level # targets (e.g., library, executable, etc.). DESCRIPTION "cstring_view, a null-terminated string view" LANGUAGES CXX - VERSION 0.0.1 + VERSION 0.1.0 ) # [CMAKE.SKIP_TESTS] @@ -26,17 +24,28 @@ option( ${PROJECT_IS_TOP_LEVEL} ) -option( - BEMAN_CSTRING_VIEW_INSTALL_CONFIG_FILE_PACKAGE - "Enable creating and installing a CMake config-file package. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." - ${PROJECT_IS_TOP_LEVEL} +# for find of beman-install-library +include(infra/cmake/beman-install-library.cmake) + +add_library(beman.cstring_view INTERFACE) +add_library(beman::cstring_view ALIAS beman.cstring_view) + +target_sources( + beman.cstring_view + PUBLIC FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +set_target_properties( + beman.cstring_view + PROPERTIES VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL} ) -include(CTest) +add_subdirectory(include/beman/cstring_view) -add_subdirectory(src/beman/cstring_view) +beman_install_library(beman.cstring_view TARGETS beman.cstring_view) if(BEMAN_CSTRING_VIEW_BUILD_TESTS) + enable_testing() add_subdirectory(tests/beman/cstring_view) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e6e0628 --- /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.cstring_view 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_CSTRING_VIEW`. +You can see the list of available options with: + +```bash +cmake -LH -S . -B build | grep "BEMAN_CSTRING_VIEW" -C 2 +``` + +
+ +Some project-specific configure arguments + +### `BEMAN_CSTRING_VIEW_BUILD_TESTS` + +Enable building tests and test infrastructure. Default: `ON`. +Values: `{ ON, OFF }`. + +### `BEMAN_CSTRING_VIEW_BUILD_EXAMPLES` + +Enable building examples. Default: `ON`. Values: `{ ON, OFF }`. + +### `BEMAN_CSTRING_VIEW_INSTALL_CONFIG_FILE_PACKAGE` + +Enable installing the CMake config file package. Default: `ON`. +Values: `{ ON, OFF }`. + +This is required so that users of `beman.cstring_view` can use +`find_package(beman.cstring_view)` to locate the library. + +
diff --git a/LICENSE b/LICENSE index 0873f35..f6db814 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,3 @@ -============================================================================== -The Beman Project is under the Apache License v2.0 with LLVM Exceptions: -============================================================================== Apache License Version 2.0, January 2004 @@ -220,15 +217,3 @@ conflicts with the conditions of the GPLv2, you may retroactively and prospectively choose to deem waived or otherwise exclude such Section(s) of the License, but only in their entirety and only with respect to the Combined Software. - -============================================================================== -Software from third parties included in the Beman Project: -============================================================================== -The Beman Project contains third party software which is under different license -terms. All such code will be identified clearly using at least one of two -mechanisms: -1) It will be in a separate directory tree with its own `LICENSE.txt` or - `LICENSE` file at the top containing the specific license and restrictions - which apply to that software, or -2) It will contain specific license and restriction terms at the top of every - file. diff --git a/README.md b/README.md index 319728b..a0def8c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ 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/dascandy/cstring_view/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/dascandy/cstring_view/actions/workflows/pre-commit.yml/badge.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/cstring_view/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/bemanproject/cstring_view/actions/workflows/pre-commit-check.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/bemanproject/cstring_view/badge.svg?branch=main)](https://coveralls.io/github/bemanproject/cstring_view?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp29.svg) [![Compiler Explorer Example](https://img.shields.io/badge/Try%20it%20on%20Compiler%20Explorer-grey?logo=compilerexplorer&logoColor=67c52a)](https://godbolt.org/z/s5KG3ozav) `beman.cstring_view` is a header-only `cstring_view` library. @@ -13,13 +13,15 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception **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.cstring_view` is licensed under the Apache License v2.0 with LLVM Exceptions. + ## Usage `std::cstring_view` exposes a string\_view like type that is intended for being able to propagate prior knowledge that a string is null-terminated throughout the type system, while fulfilling the same role as string\_view. -### Usage: default projection in constrained algorithms - The following code snippet illustrates how we can use `cstring_view` to make a beginner-friendly `main`: ```cpp @@ -39,235 +41,99 @@ Full runnable examples can be found in [`examples/`](examples/). This project requires at least the following to build: -* C++17 -* CMake 3.25 +* A C++ compiler that conforms to the C++20 standard or greater +* CMake 3.30 or later * (Test Only) GoogleTest -You can disable building tests by setting cmake option -[`BEMAN_CSTRING_VIEW_BUILD_TESTS`](#beman_cstring_view_build_tests) to `OFF` -when configuring the project. +You can disable building tests by setting CMake option `BEMAN_CSTRING_VIEW_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-14 | C++26-C++20 | libstdc++ | +| Clang | 22-19 | C++26-C++20 | libstdc++, libc++ | +| Clang | 18-17 | C++26-C++20 | libc++ | +| Clang | 18-17 | C++20 | libstdc++ | +| AppleClang | latest | C++26-C++20 | 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: +See the [Contributing Guidelines](CONTRIBUTING.md). -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/bemanproject/cstring_view) - -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 require [recent versions of CMake](#build-environment), -we advise you to download CMake directly from [CMake's website](https://cmake.org/download/) -or install it via the [Kitware apt library](https://apt.kitware.com/). +## Integrate beman.cstring_view into your project -A [supported compiler](#supported-platforms) should be available from your package manager. -Alternatively you could use an install script from official compiler vendors. +### Build -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/). +You can build cstring_view using a CMake workflow preset: ```bash -bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" +cmake --workflow --preset gcc-release ``` -If the included test suite is being built and run, a GoogleTest library will be -required. Here is an example of installing GoogleTest on a Debian-based Linux -environment: +To list available workflow presets, you can invoke: ```bash -apt install libgtest-dev +cmake --list-presets=workflow ``` -The precise command and package name will vary depending on the Linux OS you are -using. Be sure to consult documentation and the package repository for the system -you are using. +For details on building beman.cstring_view without using a CMake preset, refer to the +[Contributing Guidelines](CONTRIBUTING.md). -
+### Installation -
- For MacOS based systems - -Beman libraries require [recent versions of CMake](#build-environment). -You can use [`Homebrew`](https://brew.sh/) to install the latest major version of CMake. +To install beman.cstring_view globally after building with the `gcc-release` preset, you can +run: ```bash -brew install cmake +sudo cmake --install build/gcc-release ``` -A [supported compiler](#supported-platforms) is also available from brew. - -For example, you can install the latest major release of Clang as: +Alternatively, to install to a prefix, for example `/opt/beman`, you can run: ```bash -brew install llvm -``` - -
- -
- For Windows - -To build Beman libraries, you will need the MSVC compiler. MSVC can be obtained -by installing Visual Studio; the free Visual Studio 2022 Community Edition can -be downloaded from -[Microsoft](https://visualstudio.microsoft.com/vs/community/). - -After Visual Studio has been installed, you can launch "Developer PowerShell for -VS 2022" by typing it into Windows search bar. This shell environment will -provide CMake, Ninja, and MSVC, allowing you to build the library and run the -tests. - -Note that you will need to use FetchContent to build GoogleTest. To do so, -please see the instructions in the "Build GoogleTest dependency from github.com" -dropdown in the [Project specific configure -arguments](#project-specific-configure-arguments) section. - -
- -### 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 +sudo cmake --install build/gcc-release --prefix /opt/beman ``` -Generally, there are 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. - -> [!NOTE] -> -> The set of sanitizer supports are different across compilers. -> You can checkout the exact set of compiler arguments by looking at the toolchain -> files under the [`cmake`](cmake/) directory. +This will generate the following directory structure: -The `release` presets are designed for use in production environments, -thus they have 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. - -To configure, build and test the project with extra arguments, -you can run this set of commands. - -```bash -cmake -B build -S . -DCMAKE_CXX_STANDARD=20 # Your extra arguments here. -cmake --build build -ctest --test-dir build -``` - -> [!IMPORTANT] -> -> Beman projects are -> [passive projects](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md#cmake), -> therefore, -> you will need to specify the C++ version via `CMAKE_CXX_STANDARD` -> when manually configuring the project. - -### Finding and Fetching GTest from GitHub - -If you do not have GoogleTest installed on your development system, you may -optionally configure this project to download a known-compatible release of -GoogleTest from source and build it as well. - -Example commands: - -```shell -cmake -B build -S . \ - -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake \ - -DCMAKE_CXX_STANDARD=20 -cmake --build build --target all -cmake --build build --target test +```txt +/opt/beman +├── include +│ └── beman +│ └── cstring_view +│ ├── cstring_view.hpp +│ └── ... +└── lib + └── cmake + └── beman.cstring_view + ├── beman.cstring_view-config-version.cmake + ├── beman.cstring_view-config.cmake + └── beman.cstring_view-targets.cmake ``` -The precise version of GoogleTest that will be used is maintained in -`./lockfile.json`. - -### Project specific configure arguments +### CMake Configuration -When configuring the project manually, -you can pass an array of project specific CMake configs to customize your build. +If you installed beman.cstring_view to a prefix, you can specify that prefix to your CMake +project using `CMAKE_PREFIX_PATH`; for example, `-DCMAKE_PREFIX_PATH=/opt/beman`. -Project specific options are prefixed with `BEMAN_CSTRING_VIEW`. -You can see the list of available options with: +You need to bring in the `beman.cstring_view` package to define the `beman::cstring_view` CMake +target: -```bash -cmake -LH | grep "BEMAN_CSTRING_VIEW" -C 2 +```cmake +find_package(beman.cstring_view REQUIRED) ``` -
+You will then need to add `beman::cstring_view` to the link libraries of any libraries or +executables that include `beman.cstring_view` headers. - Details of CMake arguments. - -#### `BEMAN_CSTRING_VIEW_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_CSTRING_VIEW_BUILD_TESTS=OFF +```cmake +target_link_libraries(yourlib PUBLIC beman::cstring_view) ``` -> [!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_CSTRING_VIEW_BUILD_EXAMPLES` - -Enable building examples. Default: ON. Values: { ON, OFF }. - -
- -## Integrate beman.cstring_view into your project +### Using beman.cstring_view To use `beman.cstring_view` in your C++ project, include an appropriate `beman.cstring_view` header from your source code. @@ -278,54 +144,6 @@ include an appropriate `beman.cstring_view` header from your source code. > [!NOTE] > -> `beman.cstring_view` headers are to be included with the `beman/cstring_view/` 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.cstring_view` will depend on your build system. -CMake instructions are provided in following sections. - -### Linking your project to beman.cstring_view with CMake - -For CMake based projects, -you will need to use the `beman.cstring_view` CMake module -to define the `beman::cstring_view` CMake target: - -```cmake -find_package(beman.cstring_view REQUIRED) -``` - -You will also need to add `beman::cstring_view` to the link libraries of -any libraries or executables that include beman.cstring_view's header file. - -```cmake -target_link_libraries(yourlib PUBLIC beman::cstring_view) -``` - -### Produce beman.cstring_view static library locally - -You can include cstring_view's headers locally -by producing a static `libbeman.cstring_view.a` library. - -```bash -cmake --workflow --preset gcc-release -cmake --install build/gcc-release --prefix /opt/beman.cstring_view -``` - -This will generate such directory structure at `/opt/beman.cstring_view`. - -```txt -/opt/beman.cstring_view -├── include -│ └── beman -│ └── cstring_view -│ └── cstring_view.hpp -└── lib - └── libbeman.cstring_view.a -``` - -## Contributing - -Please do! -You encourage you to checkout our [contributor's guide](docs/README.md). -Issues and pull requests are appreciated. +> `beman.cstring_view` headers are to be included with the `beman/cstring_view/` prefix. +> Altering include search paths to spell the include target another way (e.g. +> `#include `) is unsupported. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 4dcfecc..0000000 --- a/docs/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# A guide to contribute/ Develop for Beman Project - -TODO: Currently only a skeleton. - -## Infrastructure - -### `lockfile.json` - -#### Why - -Some users and environments that are not currently using a package manager. -While CMake supports these scenarios in several ways, this project prefers to -provide and document a simple solution for those user who, reasonably, aren't -familiar with mechanisms available to configure CMake to configure a -`find_package(GTest)` command into steps that provide a GoogleTest -library fully built from source. - -As documented in this project's README, that workflow involves injecting -some custom CMake logic into the project by using the -`CMAKE_PROJECT_TOP_LEVEL_INCLUDES` CMake variable to inject a file called -`use-fetch-content.cmake` into the build of the project. Here is an example -command: - -```shell -cmake -B build -S . -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake -``` - -The precise version of GoogleTest that will be used is maintained in -`./lockfile.json`. `use-fetch-content.cmake` locates that file and configures -the project from that data. - -#### Maintenance - -Typically, the only change needed to `lockfile.json` would be updating -the commit identifiers in `git_tag` fields as appropriate. - -If, hypothetically, the project decided to add tests that use the -Catch2 test framework, a new dependency would need to be enumerated in -`lockfile.json`. A new dependency object would need to be added like so: - -```json5 - "dependencies": [ - // ... etc ... - { - "name": "Catch2", - "package_name": "Catch2", - "git_repository": "https://github.com/catchorg/Catch2", - "git_tag": "914aeecfe23b1e16af6ea675a4fb5dbd5a5b8d0a" // v3.8.0 - }, - // ... etc ... - ] -``` - -[The upstream Catch2 documentation][catch2-docs] declare that `Catch2` -is to be include with `find_package` like so: -`find_package(Catch2)`. That means the `name` field in the `lockfile.json` -dependency object is `Catch2`. That same document describes support for -`FetchContent` APIs like so: `FetchContent_Declare(Catch2 ...)`. That means -the `name` field in the `lockfile.json` dependency object is also `Catch2`. - -The `git_repository` field is the URL to the official Catch2 repository: -`https://github.com/catchorg/Catch2`. The latest release of Catch2 is -`v3.8.0`, which has the SHA `914aeecfe23b1e16af6ea675a4fb5dbd5a5b8d0a`, so -we will pin that value in `git_tag` field. - -#### Design - -This is a design for defining dependency providers -[discussed in CMake upstream documentation][dependency-providers]. The -`use-fetch-content.cmake` file *also* leverages CMake support for parsing -JSON to get the details of projects to provide from `lockfile.json`. This: - -* Ensures that calls to FetchContent APIs within this project are consistent - and meet Beman Standards. - -* Provides a proof-of-concept for a utility that could potentially be used - across all Beman libraries, reducing the complexity of each project. - -* Avoids churn in CMake files simply because a version of a dependency - needs updated. - -* Eliminates a significant requirement for any potential automation for - bumping the version of dependencies -- the need to parse and transform - files written in the CMake syntax. - -#### JSON Structure - -The `lockfile.json` file contains an object with one field named `dependencies`. - -`dependencies` should have a value that is an array of objects. - -Each dependency object should contain exactly four fields with string values: - -* `name` is used as the FetchContent name for the project. See - [the API docs for FetchContent][fetch-content] for more on what a "FetchContent - name" is. - -* `package_name` *must* match the upstream-documented "package name" that would - be provided to a `find_package` call. For GoogleTest, this is `GTest`, for instance. - -* `git_repository` is a full https URL for the repository to clone. - -* `git_tag` must be a valid git ref in that repository. This identifies precisely which - version of the dependency to build. While branch and tag names will work for this value, - the Beman Project prefers the stability provided by a full-length git commit ID, so - please use one of those in any changes submitted to `lockfile.json`. - -### Lint - -[catch2-docs]: https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md#cmake-targets - -[dependency-providers]: https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html#dependency-providers] - -[fetch-content]: https://cmake.org/cmake/help/latest/module/FetchContent.html diff --git a/examples/example.cpp b/examples/example.cpp index 5811bc2..dda6be0 100644 --- a/examples/example.cpp +++ b/examples/example.cpp @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + #include #include diff --git a/include/beman/cstring_view/CMakeLists.txt b/include/beman/cstring_view/CMakeLists.txt new file mode 100644 index 0000000..9fb32d1 --- /dev/null +++ b/include/beman/cstring_view/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +target_sources( + beman.cstring_view + PUBLIC FILE_SET HEADERS FILES cstring_view.hpp +) diff --git a/include/beman/cstring_view/cstring_view.hpp b/include/beman/cstring_view/cstring_view.hpp index e71da41..a698e6b 100644 --- a/include/beman/cstring_view/cstring_view.hpp +++ b/include/beman/cstring_view/cstring_view.hpp @@ -1,5 +1,7 @@ -#ifndef BEMAN_CSTRING_VIEW_HPP -#define BEMAN_CSTRING_VIEW_HPP +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef BEMAN_CSTRING_VIEW_CSTRING_VIEW_HPP +#define BEMAN_CSTRING_VIEW_CSTRING_VIEW_HPP #include #include @@ -63,15 +65,15 @@ struct hash; inline namespace literals { inline namespace cstring_view_literals { #ifndef _MSC_VER -#pragma GCC diagnostic push -#ifdef __clang__ -#pragma GCC diagnostic ignored "-Wuser-defined-literals" -#else -#pragma GCC diagnostic ignored "-Wliteral-suffix" -#endif + #pragma GCC diagnostic push + #ifdef __clang__ + #pragma GCC diagnostic ignored "-Wuser-defined-literals" + #else + #pragma GCC diagnostic ignored "-Wliteral-suffix" + #endif #else -#pragma warning(push) -#pragma warning(disable : 4455) + #pragma warning(push) + #pragma warning(disable : 4455) #endif // [cstring.view.literals], suffix for basic_cstring_view literals constexpr cstring_view operator"" csv(const char* str, size_t len) noexcept; @@ -80,9 +82,9 @@ constexpr u16cstring_view operator"" csv(const char16_t* str, size_t len) noexce constexpr u32cstring_view operator"" csv(const char32_t* str, size_t len) noexcept; constexpr wcstring_view operator"" csv(const wchar_t* str, size_t len) noexcept; #ifndef _MSC_VER -#pragma GCC diagnostic pop + #pragma GCC diagnostic pop #else -#pragma warning(pop) + #pragma warning(pop) #endif } // namespace cstring_view_literals } // namespace literals @@ -108,11 +110,12 @@ class basic_cstring_view { using difference_type = ptrdiff_t; static constexpr size_type npos = size_type(-1); + private: + static constexpr charT empty_string[1]{}; + + public: // [cstring.view.cons], construction and assignment - constexpr basic_cstring_view() noexcept : size_() { - static const charT empty_string[1]{}; - data_ = std::data(empty_string); - } + constexpr basic_cstring_view() noexcept : size_() { data_ = std::data(empty_string); } constexpr basic_cstring_view(const basic_cstring_view&) noexcept = default; constexpr basic_cstring_view& operator=(const basic_cstring_view&) noexcept = default; constexpr basic_cstring_view(const charT* str) : basic_cstring_view(str, traits::length(str)) {} @@ -389,4 +392,4 @@ struct std::hash { auto operator()(const beman::wcstring_view& sv) const noexcept { return std::hash{}(sv); } }; -#endif +#endif // BEMAN_CSTRING_VIEW_CSTRING_VIEW_HPP diff --git a/infra/.beman_submodule b/infra/.beman_submodule index bfed167..28a0600 100644 --- a/infra/.beman_submodule +++ b/infra/.beman_submodule @@ -1,3 +1,3 @@ [beman_submodule] remote=https://github.com/bemanproject/infra.git -commit_hash=bb58b2a1cc894d58a55bf745be78f5d27029e245 +commit_hash=63cb577f6484f13ce3349de49ad5ce27e20bf1da diff --git a/infra/.github/workflows/beman-submodule.yml b/infra/.github/workflows/beman-submodule.yml deleted file mode 100644 index 8435086..0000000 --- a/infra/.github/workflows/beman-submodule.yml +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -name: beman-submodule tests - -on: - push: - branches: - - main - 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/.pre-commit-config.yaml b/infra/.pre-commit-config.yaml index e806e59..bc4dd84 100644 --- a/infra/.pre-commit-config.yaml +++ b/infra/.pre-commit-config.yaml @@ -19,14 +19,3 @@ repos: - id: gersemi name: CMake linting exclude: ^.*/tests/.*/data/ # Exclude test data directories - - # Python linting and formatting - # config file: ruff.toml (not currently present but add if needed) - # https://docs.astral.sh/ruff/configuration/ - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.2 - hooks: - - id: ruff-check - files: ^tools/beman-tidy/ - - id: ruff-format - files: ^tools/beman-tidy/ diff --git a/infra/.pre-commit-hooks.yaml b/infra/.pre-commit-hooks.yaml deleted file mode 100644 index d327587..0000000 --- a/infra/.pre-commit-hooks.yaml +++ /dev/null @@ -1,7 +0,0 @@ -- id: beman-tidy - name: "beman-tidy: bemanification your repo" - entry: ./tools/beman-tidy/beman-tidy - language: script - pass_filenames: false - always_run: true - args: [".", "--verbose"] diff --git a/infra/README.md b/infra/README.md index 16b2672..a869e46 100644 --- a/infra/README.md +++ b/infra/README.md @@ -9,12 +9,11 @@ so it does not respect the usual structure of a Beman library repository nor The * `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](./tools/beman-submodule/README.md) for details. +[the beman-submodule documentation](https://github.com/bemanproject/beman-submodule) for details. ### CMake Modules diff --git a/infra/cmake/Config.cmake.in b/infra/cmake/Config.cmake.in new file mode 100644 index 0000000..81adf80 --- /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_FIND_DEPENDENCIES@ + +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) + +check_required_components(@PROJECT_NAME@) diff --git a/infra/cmake/beman-install-library-config.cmake b/infra/cmake/beman-install-library-config.cmake deleted file mode 100644 index e7fd0ad..0000000 --- a/infra/cmake/beman-install-library-config.cmake +++ /dev/null @@ -1,169 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -include_guard(GLOBAL) - -# This file defines the function `beman_install_library` which is used to -# install a library target and its headers, along with optional CMake -# configuration files. -# -# The function is designed to be reusable across different Beman libraries. - -function(beman_install_library name) - # Usage - # ----- - # - # beman_install_library(NAME) - # - # Brief - # ----- - # - # This function installs the specified library target and its headers. - # It also handles the installation of the CMake configuration files if needed. - # - # CMake variables - # --------------- - # - # Note that configuration of the installation is generally controlled by CMake - # cache variables so that they can be controlled by the user or tool running the - # `cmake` command. Neither `CMakeLists.txt` nor `*.cmake` files should set these - # variables directly. - # - # - BEMAN_INSTALL_CONFIG_FILE_PACKAGES: - # List of packages that require config file installation. - # If the package name is in this list, it will install the config file. - # - # - _INSTALL_CONFIG_FILE_PACKAGE: - # Boolean to control config file installation for the specific library. - # The prefix `` is the uppercased name of the library with dots - # replaced by underscores. - # - if(NOT TARGET "${name}") - message(FATAL_ERROR "Target '${name}' does not exist.") - endif() - - if(NOT ARGN STREQUAL "") - message( - FATAL_ERROR - "beman_install_library does not accept extra arguments: ${ARGN}" - ) - endif() - - # Given foo.bar, the component name is bar - string(REPLACE "." ";" name_parts "${name}") - # 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 expects a name of the form 'beman.', got '${name}'" - ) - endif() - - set(target_name "${name}") - set(install_component_name "${name}") - set(export_name "${name}") - set(package_name "${name}") - list(GET name_parts -1 component_name) - - install( - TARGETS "${target_name}" - COMPONENT "${install_component_name}" - EXPORT "${export_name}" - FILE_SET HEADERS - ) - - set_target_properties( - "${target_name}" - PROPERTIES EXPORT_NAME "${component_name}" - ) - - include(GNUInstallDirs) - - # Determine the prefix for project-specific variables - string(TOUPPER "${name}" project_prefix) - string(REPLACE "." "_" project_prefix "${project_prefix}") - - option( - ${project_prefix}_INSTALL_CONFIG_FILE_PACKAGE - "Enable building examples. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." - ${PROJECT_IS_TOP_LEVEL} - ) - - # By default, install the config package - set(install_config_package ON) - - # Turn OFF installation of config package by default if, - # in order of precedence: - # 1. The specific package variable is set to OFF - # 2. The package name is not in the list of packages to install config files - if(DEFINED BEMAN_INSTALL_CONFIG_FILE_PACKAGES) - if( - NOT "${install_component_name}" - IN_LIST - BEMAN_INSTALL_CONFIG_FILE_PACKAGES - ) - set(install_config_package OFF) - endif() - endif() - if(DEFINED ${project_prefix}_INSTALL_CONFIG_FILE_PACKAGE) - set(install_config_package - ${${project_prefix}_INSTALL_CONFIG_FILE_PACKAGE} - ) - endif() - - if(install_config_package) - message( - DEBUG - "beman-install-library: Installing a config package for '${name}'" - ) - - include(CMakePackageConfigHelpers) - - find_file( - config_file_template - NAMES "${package_name}-config.cmake.in" - PATHS "${CMAKE_CURRENT_SOURCE_DIR}" - NO_DEFAULT_PATH - NO_CACHE - REQUIRED - ) - set(config_package_file - "${CMAKE_CURRENT_BINARY_DIR}/${package_name}-config.cmake" - ) - set(package_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}") - configure_package_config_file( - "${config_file_template}" - "${config_package_file}" - INSTALL_DESTINATION "${package_install_dir}" - PATH_VARS PROJECT_NAME PROJECT_VERSION - ) - - set(config_version_file - "${CMAKE_CURRENT_BINARY_DIR}/${package_name}-config-version.cmake" - ) - write_basic_package_version_file( - "${config_version_file}" - VERSION "${PROJECT_VERSION}" - COMPATIBILITY ExactVersion - ) - - install( - FILES "${config_package_file}" "${config_version_file}" - DESTINATION "${package_install_dir}" - COMPONENT "${install_component_name}" - ) - - set(config_targets_file "${package_name}-targets.cmake") - install( - EXPORT "${export_name}" - DESTINATION "${package_install_dir}" - NAMESPACE beman:: - FILE "${config_targets_file}" - COMPONENT "${install_component_name}" - ) - else() - message( - DEBUG - "beman-install-library: Not installing a config package for '${name}'" - ) - endif() -endfunction() diff --git a/infra/cmake/beman-install-library.cmake b/infra/cmake/beman-install-library.cmake new file mode 100644 index 0000000..8a6c5a1 --- /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 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 `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 + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + if(NOT BEMAN_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_TARGETS}' due to CMAKE_SKIP_INSTALL_RULES" + ) + return() + endif() + + set(_config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${name}") + + # ---------------------------- + # Defaults + # ---------------------------- + if(NOT BEMAN_NAMESPACE) + set(BEMAN_NAMESPACE "beman::") + endif() + + if(NOT BEMAN_EXPORT_NAME) + set(BEMAN_EXPORT_NAME "${name}-targets") + endif() + + if(NOT BEMAN_DESTINATION) + set(BEMAN_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_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 C++ module file sets, exact one expected! + get_target_property(_module_sets "${_tgt}" CXX_MODULE_SETS) + if(_module_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has CXX_MODULE_SETS=${_module_sets}" + ) + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_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_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_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_EXPORT_NAME} + NAMESPACE ${BEMAN_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_DEPENDENCIES) + message( + VERBOSE + "beman-install-library(${name}): Add find_dependency(${dep})" + ) + string(APPEND _beman_find_deps "find_dependency(${dep})\n") + endforeach() + set(BEMAN_FIND_DEPENDENCIES "${_beman_find_deps}") + + # ---------------------------------------- + # Generate + install config files + # ---------------------------------------- + if(_install_config) + 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/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 66cb96e..0000000 --- a/infra/tools/beman-submodule/beman-submodule +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python3 - -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -import argparse -import configparser -import filecmp -import glob -import os -import shutil -import subprocess -import sys -import tempfile -from pathlib import Path - - -def directory_compare( - reference: str | Path, actual: str | Path, ignore, allow_untracked_files: bool): - reference, actual = Path(reference), Path(actual) - - compared = filecmp.dircmp(reference, actual, ignore=ignore) - if (compared.left_only - or (compared.right_only and not allow_untracked_files) - or compared.diff_files): - return False - for common_dir in compared.common_dirs: - path1 = reference / common_dir - path2 = actual / common_dir - if not directory_compare(path1, path2, ignore, allow_untracked_files): - return False - return True - -class BemanSubmodule: - def __init__( - self, dirpath: str | Path, remote: str, commit_hash: str, - allow_untracked_files: bool): - self.dirpath = Path(dirpath) - self.remote = remote - self.commit_hash = commit_hash - self.allow_untracked_files = allow_untracked_files - -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() - allow_untracked_files = config.getboolean( - 'beman_submodule', 'allow_untracked_files', fallback=False) - return BemanSubmodule( - Path(path).resolve().parent, - config['beman_submodule']['remote'], - config['beman_submodule']['commit_hash'], - allow_untracked_files) - -def get_beman_submodule(path: str | Path): - beman_submodule_filepath = Path(path) / '.beman_submodule' - - if beman_submodule_filepath.is_file(): - return parse_beman_submodule_file(beman_submodule_filepath) - else: - return None - -def find_beman_submodules_in(path): - path = Path(path) - assert path.is_dir() - - result = [] - for dirpath, _, filenames in path.walk(): - if '.beman_submodule' in filenames: - result.append(parse_beman_submodule_file(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 get_paths(beman_submodule): - tmpdir = clone_beman_submodule_into_tmpdir(beman_submodule, False) - paths = set(glob.glob('*', root_dir=Path(tmpdir.name), include_hidden=True)) - paths.remove('.git') - return paths - -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'], - beman_submodule.allow_untracked_files): - 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 = Path(beman_submodule.dirpath).relative_to(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) - tmp_path = Path(tmpdir.name) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmp_path) - - if beman_submodule.allow_untracked_files: - for path in get_paths(beman_submodule): - path2 = Path(beman_submodule.dirpath) / path - if Path(path2).is_dir(): - shutil.rmtree(path2) - elif Path(path2).is_file(): - os.remove(path2) - else: - shutil.rmtree(beman_submodule.dirpath) - - submodule_path = tmp_path / '.beman_submodule' - with open(submodule_path, 'w') as f: - f.write('[beman_submodule]\n') - f.write(f'remote={beman_submodule.remote}\n') - f.write(f'commit_hash={sha_process.stdout.strip()}\n') - if beman_submodule.allow_untracked_files: - f.write(f'allow_untracked_files=True\n') - shutil.rmtree(tmp_path / '.git') - shutil.copytree(tmp_path, beman_submodule.dirpath, dirs_exist_ok=True) - -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, allow_untracked_files): - 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 = Path(repository_name) - else: - path = Path(path) - if not allow_untracked_files and path.exists(): - raise Exception(f'{path} exists') - path.mkdir(exist_ok=allow_untracked_files) - tmpdir_repo = Path(tmpdir.name) / repository_name - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir_repo) - with open(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') - if allow_untracked_files: - f.write(f'allow_untracked_files=True\n') - shutil.rmtree(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_add.add_argument( - '--allow-untracked-files', action='store_true', - help='the beman_submodule will not occupy the subdirectory exclusively') - 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, args.allow_untracked_files) - 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 600fc07..0000000 --- a/infra/tools/beman-submodule/test/test_beman_submodule.py +++ /dev/null @@ -1,539 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -import glob -import os -import pytest -import shutil -import stat -import subprocess -import tempfile -from pathlib import Path - -# https://stackoverflow.com/a/19011259 -import types -import importlib.machinery -loader = importlib.machinery.SourceFileLoader( - 'beman_submodule', - str(Path(__file__).parent.resolve().parent / 'beman-submodule')) -beman_submodule = types.ModuleType(loader.name) -loader.exec_module(beman_submodule) - -def create_test_git_repository(): - tmpdir = tempfile.TemporaryDirectory() - tmp_path = Path(tmpdir.name) - - subprocess.run(['git', 'init'], check=True, cwd=tmpdir.name, capture_output=True) - def make_commit(a_txt_contents): - with open(tmp_path / '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 create_test_git_repository2(): - tmpdir = tempfile.TemporaryDirectory() - tmp_path = Path(tmpdir.name) - - subprocess.run(['git', 'init'], check=True, cwd=tmpdir.name, capture_output=True) - with open(tmp_path / 'a.txt', 'w') as f: - f.write('a') - 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) - os.remove(tmp_path / 'a.txt') - subprocess.run( - ['git', 'rm', 'a.txt'], check=True, cwd=tmpdir.name, capture_output=True) - with open(tmp_path / 'b.txt', 'w') as f: - f.write('b') - subprocess.run( - ['git', 'add', 'b.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) - return tmpdir - -def test_directory_compare(): - def create_dir_structure(dir_path: Path): - bar_path = dir_path / 'bar' - os.makedirs(bar_path) - - with open(dir_path / 'foo.txt', 'w') as f: - f.write('foo') - with open(bar_path / 'baz.txt', 'w') as f: - f.write('baz') - - with tempfile.TemporaryDirectory() as dir_a, \ - tempfile.TemporaryDirectory() as dir_b: - path_a = Path(dir_a) - path_b = Path(dir_b) - - create_dir_structure(path_a) - create_dir_structure(path_b) - - assert beman_submodule.directory_compare(dir_a, dir_b, [], False) - - with open(path_a / 'bar' / 'quux.txt', 'w') as f: - f.write('quux') - - assert not beman_submodule.directory_compare(path_a, path_b, [], False) - assert beman_submodule.directory_compare(path_a, path_b, ['quux.txt'], False) - -def test_directory_compare_untracked_files(): - def create_dir_structure(dir_path: Path): - bar_path = dir_path / 'bar' - os.makedirs(bar_path) - - with open(dir_path / 'foo.txt', 'w') as f: - f.write('foo') - with open(bar_path / 'baz.txt', 'w') as f: - f.write('baz') - - with tempfile.TemporaryDirectory() as reference, \ - tempfile.TemporaryDirectory() as actual: - path_a = Path(reference) - path_b = Path(actual) - - create_dir_structure(path_a) - create_dir_structure(path_b) - (path_b / 'c.txt').touch() - - assert beman_submodule.directory_compare(reference, actual, [], True) - - with open(path_a / 'bar' / 'quux.txt', 'w') as f: - f.write('quux') - - assert not beman_submodule.directory_compare(path_a, path_b, [], True) - assert beman_submodule.directory_compare(path_a, path_b, ['quux.txt'], True) - -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 == 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 = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - 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 = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - 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 == Path(tmpdir2.name) / 'bar' - assert beman_submodules[0].remote == tmpdir.name - assert beman_submodules[0].commit_hash == sha - assert beman_submodules[1].dirpath == Path(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 = Path.cwd() - 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 = Path.cwd() - 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', False) - module = beman_submodule.get_beman_submodule(Path(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'], False) - tmpdir4 = beman_submodule.clone_beman_submodule_into_tmpdir(module, True) - assert beman_submodule.directory_compare(tmpdir.name, tmpdir4.name, ['.git'], False) - subprocess.run( - ['git', 'reset', '--hard', sha], capture_output=True, check=True, - cwd=tmpdir.name) - assert beman_submodule.directory_compare(tmpdir.name, tmpdir3.name, ['.git'], False) - os.chdir(original_cwd) - -def test_get_paths(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - module = beman_submodule.get_beman_submodule(Path(tmpdir2.name) / 'foo') - assert beman_submodule.get_paths(module) == set(['a.txt']) - os.chdir(original_cwd) - -def test_beman_submodule_status(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - 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(Path(tmpdir2.name) / 'foo')) - with open(Path(tmpdir2.name) / 'foo' / 'a.txt', 'w') as f: - f.write('b') - assert '+ ' + sha + ' foo' == beman_submodule.beman_submodule_status( - beman_submodule.get_beman_submodule(Path(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 = Path.cwd() - os.chdir(tmpdir2.name) - orig_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - orig_sha = orig_sha_process.stdout.strip() - parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_sha = parent_sha_process.stdout.strip() - parent_parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_parent_sha = parent_parent_sha_process.stdout.strip() - subprocess.run( - ['git', 'reset', '--hard', parent_parent_sha], capture_output=True, check=True, - cwd=tmpdir.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - subprocess.run( - ['git', 'reset', '--hard', orig_sha], capture_output=True, check=True, - cwd=tmpdir.name) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n') - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n') - beman_submodule.update_command(False, None) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - subprocess.run( - ['git', 'reset', '--hard', parent_sha], capture_output=True, check=True, - cwd=tmpdir.name) - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'bar', ['.git', '.beman_submodule'], False) - subprocess.run( - ['git', 'reset', '--hard', orig_sha], capture_output=True, check=True, - cwd=tmpdir.name) - beman_submodule.update_command(True, None) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={orig_sha}\n' - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={orig_sha}\n' - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'bar', ['.git', '.beman_submodule'], False) - os.chdir(original_cwd) - -def test_update_command_with_path(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - orig_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - orig_sha = orig_sha_process.stdout.strip() - parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_sha = parent_sha_process.stdout.strip() - parent_parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_parent_sha = parent_parent_sha_process.stdout.strip() - subprocess.run( - ['git', 'reset', '--hard', parent_parent_sha], capture_output=True, check=True, - cwd=tmpdir.name) - tmpdir_parent_parent_copy = tempfile.TemporaryDirectory() - shutil.copytree(tmpdir.name, tmpdir_parent_parent_copy.name, dirs_exist_ok=True) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - subprocess.run( - ['git', 'reset', '--hard', orig_sha], capture_output=True, check=True, - cwd=tmpdir.name) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n') - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n') - beman_submodule.update_command(False, 'foo') - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - subprocess.run( - ['git', 'reset', '--hard', parent_sha], capture_output=True, check=True, - cwd=tmpdir.name) - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - assert beman_submodule.directory_compare( - tmpdir_parent_parent_copy.name, - Path(tmpdir2.name) / 'bar', ['.git', '.beman_submodule'], False) - subprocess.run( - ['git', 'reset', '--hard', orig_sha], capture_output=True, check=True, - cwd=tmpdir.name) - beman_submodule.update_command(True, 'foo') - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={orig_sha}\n' - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - assert beman_submodule.directory_compare( - tmpdir_parent_parent_copy.name, - Path(tmpdir2.name) / 'bar', ['.git', '.beman_submodule'], False) - os.chdir(original_cwd) - -def test_update_command_untracked_files(): - tmpdir = create_test_git_repository2() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd(); - os.chdir(tmpdir2.name) - orig_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - orig_sha = orig_sha_process.stdout.strip() - parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_sha = parent_sha_process.stdout.strip() - os.makedirs(Path(tmpdir2.name) / 'foo') - (Path(tmpdir2.name) / 'foo' / 'c.txt').touch() - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\nallow_untracked_files=True') - beman_submodule.update_command(False, 'foo') - assert set(['./foo/a.txt', './foo/c.txt']) == set(glob.glob('./foo/*.txt')) - beman_submodule.update_command(True, 'foo') - assert set(['./foo/b.txt', './foo/c.txt']) == set(glob.glob('./foo/*.txt')) - os.chdir(original_cwd) - -def test_add_command(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - 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, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - with open(Path(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_add_command_untracked_files(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - os.makedirs(Path(tmpdir2.name) / 'foo') - (Path(tmpdir2.name) / 'foo' / 'c.txt').touch() - beman_submodule.add_command(tmpdir.name, 'foo', True) - assert set(['./foo/a.txt', './foo/c.txt']) == set(glob.glob('./foo/*.txt')) - os.chdir(original_cwd) - -def test_status_command_no_paths(capsys): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - with open(Path(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 = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - with open(Path(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_status_command_untracked_files(capsys): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', True) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - (Path(tmpdir2.name) / 'foo' / 'c.txt').touch() - beman_submodule.status_command(['foo']) - sha = sha_process.stdout.strip() - assert capsys.readouterr().out == ' ' + sha + ' foo\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 = Path(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/src/beman/cstring_view/CMakeLists.txt b/src/beman/cstring_view/CMakeLists.txt deleted file mode 100644 index 350759f..0000000 --- a/src/beman/cstring_view/CMakeLists.txt +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -include(GNUInstallDirs) - -add_library(beman.cstring_view) -add_library(beman::cstring_view ALIAS beman.cstring_view) - -target_sources(beman.cstring_view PRIVATE cstring_view.cpp) - -target_sources( - beman.cstring_view - PUBLIC - FILE_SET HEADERS - BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../../../include - FILES - ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/beman/cstring_view/cstring_view.hpp -) - -set_target_properties( - beman.cstring_view - PROPERTIES VERIFY_INTERFACE_HEADER_SETS ON EXPORT_NAME cstring_view -) - -install( - TARGETS beman.cstring_view - COMPONENT beman.cstring_view - EXPORT beman.cstring_view - DESTINATION ${CMAKE_INSTALL_LIBDIR}$<$:/debug> - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}$<$:/debug> - FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) - -if(BEMAN_CSTRING_VIEW_INSTALL_CONFIG_FILE_PACKAGE) - include(CMakePackageConfigHelpers) - - configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/beman.cstring_view-config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/beman.cstring_view-config.cmake" - INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/beman.cstring_view" - PATH_VARS PROJECT_NAME PROJECT_VERSION - ) - - write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/beman.cstring_view-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY ExactVersion - ) - - install( - FILES - "${CMAKE_CURRENT_BINARY_DIR}/beman.cstring_view-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/beman.cstring_view-config-version.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/beman.cstring_view" - COMPONENT beman.cstring_view - ) - - install( - EXPORT beman.cstring_view - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/beman.cstring_view" - NAMESPACE beman:: - FILE beman.cstring_view-targets.cmake - COMPONENT beman.cstring_view - ) -endif() diff --git a/src/beman/cstring_view/beman.cstring_view-config.cmake.in b/src/beman/cstring_view/beman.cstring_view-config.cmake.in deleted file mode 100644 index 709a6f8..0000000 --- a/src/beman/cstring_view/beman.cstring_view-config.cmake.in +++ /dev/null @@ -1,7 +0,0 @@ -set(BEMAN_CSTRING_VIEW_VERSION @PROJECT_VERSION@) - -@PACKAGE_INIT@ - -include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) - -check_required_components(@PROJECT_NAME@) diff --git a/src/beman/cstring_view/cstring_view.cpp b/src/beman/cstring_view/cstring_view.cpp deleted file mode 100644 index 0d608d5..0000000 --- a/src/beman/cstring_view/cstring_view.cpp +++ /dev/null @@ -1 +0,0 @@ -#include diff --git a/tests/beman/cstring_view/CMakeLists.txt b/tests/beman/cstring_view/CMakeLists.txt index cf5d105..8634c70 100644 --- a/tests/beman/cstring_view/CMakeLists.txt +++ b/tests/beman/cstring_view/CMakeLists.txt @@ -2,12 +2,15 @@ find_package(GTest REQUIRED) -add_executable(beman.cstring_view.tests.identity) -target_sources(beman.cstring_view.tests.identity PRIVATE cstring_view.test.cpp) +add_executable(beman.cstring_view.tests.cstring_view) +target_sources( + beman.cstring_view.tests.cstring_view + PRIVATE cstring_view.test.cpp +) target_link_libraries( - beman.cstring_view.tests.identity + beman.cstring_view.tests.cstring_view PRIVATE beman::cstring_view GTest::gtest GTest::gtest_main ) include(GoogleTest) -gtest_discover_tests(beman.cstring_view.tests.identity) +gtest_discover_tests(beman.cstring_view.tests.cstring_view) diff --git a/tests/beman/cstring_view/cstring_view.test.cpp b/tests/beman/cstring_view/cstring_view.test.cpp index 9d7e8b7..73c13df 100644 --- a/tests/beman/cstring_view/cstring_view.test.cpp +++ b/tests/beman/cstring_view/cstring_view.test.cpp @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + #include #include #include