diff --git a/.clang-format b/.clang-format index 05baf03..74f95dc 100644 --- a/.clang-format +++ b/.clang-format @@ -125,7 +125,7 @@ IndentCaseBlocks: false IndentCaseLabels: false IndentExternBlock: AfterExternBlock IndentGotoLabels: true -IndentPPDirectives: None +IndentPPDirectives: BeforeHash IndentRequiresClause: true IndentWidth: 4 IndentWrappedFunctionNames: false diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 0eb24e3..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/cpp:1-ubuntu-24.04 - -USER vscode - -# Install latest cmake -RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null -RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ noble main' | sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null -RUN sudo apt-get update && sudo apt-get install -y cmake - -# Install pre-commit -RUN sudo apt-get install -y pipx && pipx install pre-commit - -# Newer gcc/ llvm is needed to avoid ASAN Stalling -# See: https://github.com/google/sanitizers/issues/1614 -# Minimal version: clang-18.1.3, gcc-13.2 -RUN sudo apt-get install -y gcc-14 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 07344f6..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,18 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/cpp - -{ - "name": "Beman Project Generic Devcontainer", - "build": { - "dockerfile": "Dockerfile" - }, - "postCreateCommand": "bash .devcontainer/postcreate.sh", - "customizations": { - "vscode": { - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools" - ] - } - } -} diff --git a/.devcontainer/postcreate.sh b/.devcontainer/postcreate.sh deleted file mode 100644 index bddba03..0000000 --- a/.devcontainer/postcreate.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Setup pre-commit -pre-commit -pre-commit install diff --git a/.exemplar_version b/.exemplar_version new file mode 100644 index 0000000..447909a --- /dev/null +++ b/.exemplar_version @@ -0,0 +1 @@ +ab5c7c0cbf1f67eb43b7be9c2d18acd4d6de1ea4 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a99da1c..5ac72a7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,23 +1,3 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# Codeowners for reviews on PRs -# Note(river): -# **Please understand how codeowner file work before uncommenting anything in this section:** -# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -# -# For projects using exemplar as a template and intend to reuse its infrastructure, -# River (@wusatosi) helped create most of the original infrastructure under the scope described below, -# they are more than happy to help out with any PRs downstream, -# as well as to sync any useful change upstream to exemplar. -# -# Github Actions: -# .github/workflows/ @wusatosi # Add other project owners here -# -# Devcontainer: -# .devcontainer/ @wusatosi # Add other project owners here -# -# Pre-commit: -# .pre-commit-config.yaml @wusatosi # Add other project owners here -# .markdownlint.yaml @wusatosi # Add other project owners here - -* @neatudarius @wusatosi @JeffGarland @changkhothuychung +* @changkhothuychung diff --git a/.github/ISSUE_TEMPLATE/implementation-deficiency.md b/.github/ISSUE_TEMPLATE/implementation-deficiency.md new file mode 100644 index 0000000..4a0ee77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/implementation-deficiency.md @@ -0,0 +1,35 @@ +--- +name: Implementation Deficiency +about: Report a bug or performance issue of our implementation +title: '' +labels: bug +assignees: '' + +--- + + + +## Describe the deficiency + +A clear and concise description of what the deficiency is. +Link all relevant issues. +This could be a bug, or a performance problem. + +## To Reproduce + +```c++ +// Use case +``` + +## Expected Behavior + +A clear and concise description of what you expected to happen. + +## Additional Discussions + +Add any other context about the problem here. +If you believe your issue is platform dependent, +please post your compiler versions here. diff --git a/.github/ISSUE_TEMPLATE/infrastructure-issues.md b/.github/ISSUE_TEMPLATE/infrastructure-issues.md new file mode 100644 index 0000000..11fbd13 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/infrastructure-issues.md @@ -0,0 +1,29 @@ +--- +name: Infrastructure Issues +about: Report a bug or feature request with our Infrastructure +title: '' +labels: infra +assignees: '' + +--- + + + +## I am attempting to + +Describe what you were attempting to do. + +## Expected Behavior + +A clear and concise description of what you expected to happen. + +## Current Behavior + +A clear and concise description of what actually happened. + +## Additional Discussions + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/paper-discussion.md b/.github/ISSUE_TEMPLATE/paper-discussion.md new file mode 100644 index 0000000..14c9e4f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/paper-discussion.md @@ -0,0 +1,33 @@ +--- +name: Paper Discussion +about: Provide feedback to current API +title: '' +labels: '' +assignees: '' + +--- + + + +## Use case + +Describe your concerns about adding this change to the C++ Standard Library. + +```c++ +// example snippet +``` + +## What I like + +Let us know what you find positive about current approach / design. + +## What I dislike + +Let us know what you find negative about current approach / design. + +## Discussion + +Let us know if you have any more remarks. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..071cb28 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,5 @@ + diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index b9c0c01..2dd7efa 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -9,14 +9,14 @@ on: pull_request: workflow_dispatch: schedule: - - cron: '30 15 * * *' + - cron: '16 15 * * 2' jobs: beman-submodule-check: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.1.0 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.5.3 preset-test: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.1.0 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.5.3 with: matrix_config: > [ @@ -27,7 +27,7 @@ jobs: ] build-and-test: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.1.0 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-build-and-test.yml@1.5.3 with: matrix_config: > { @@ -93,4 +93,4 @@ jobs: create-issue-when-fault: needs: [preset-test, build-and-test] if: failure() && github.event_name == 'schedule' - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.1.0 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-create-issue-when-fault.yml@1.5.3 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit-check.yml similarity index 71% rename from .github/workflows/pre-commit.yml rename to .github/workflows/pre-commit-check.yml index 70895b4..980f6c5 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit-check.yml @@ -8,6 +8,12 @@ on: branches: - main +permissions: + contents: read + checks: write + issues: write + pull-requests: write + jobs: pre-commit: - uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.1.0 + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-pre-commit.yml@1.5.3 diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml new file mode 100644 index 0000000..e5dc609 --- /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: "8 14 * * 4" + +jobs: + auto-update-pre-commit: + uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-update-pre-commit.yml@1.5.3 + secrets: + APP_ID: ${{ secrets.AUTO_PR_BOT_APP_ID }} + PRIVATE_KEY: ${{ secrets.AUTO_PR_BOT_PRIVATE_KEY }} diff --git a/.gitignore b/.gitignore index 0cb4a66..d293e3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +/.cache /compile_commands.json /build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e0f5998..eb01186 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,17 +13,18 @@ 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.4 hooks: - id: clang-format types_or: [c++, c] # CMake linting and formatting - - repo: https://github.com/BlankSpruce/gersemi - rev: 0.22.3 + - repo: https://github.com/BlankSpruce/gersemi-pre-commit + rev: 0.27.2 hooks: - id: gersemi name: CMake linting + exclude: ^.*/tests/.*/data/ # Exclude test data directories # Markdown linting # Config file: .markdownlint.yaml @@ -34,7 +35,7 @@ repos: # - id: markdownlint - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell @@ -43,3 +44,5 @@ repos: rev: v0.3.1 hooks: - id: beman-tidy + +exclude: 'cookiecutter/|infra/' diff --git a/CMakeLists.txt b/CMakeLists.txt index 534b8f4..1347805 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,56 +1,54 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.30...4.3) project( beman.cache_latest # CMake Project Name, which is also the name of the top-level # targets (e.g., library, executable, etc.). - DESCRIPTION "A Beman library cache_latest" + DESCRIPTION + "A C++ ranges adaptor that caches the result of the last dereference of the underlying iterator" LANGUAGES CXX - VERSION 1.0.0 + VERSION 0.1.0 ) -enable_testing() - # [CMAKE.SKIP_TESTS] option( BEMAN_CACHE_LATEST_BUILD_TESTS - "Enable building tests and test infrastructure. Default: ON. Values: { ON, OFF }." + "Enable building tests and test infrastructure. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." ${PROJECT_IS_TOP_LEVEL} ) # [CMAKE.SKIP_EXAMPLES] option( BEMAN_CACHE_LATEST_BUILD_EXAMPLES - "Enable building examples. Default: ON. Values: { ON, OFF }." + "Enable building examples. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." ${PROJECT_IS_TOP_LEVEL} ) -option( - BEMAN_CACHE_LATEST_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 and configure_build_telemetry +include(infra/cmake/beman-install-library.cmake) +include(infra/cmake/BuildTelemetryConfig.cmake) + +add_library(beman.cache_latest INTERFACE) +add_library(beman::cache_latest ALIAS beman.cache_latest) + +target_sources( + beman.cache_latest + PUBLIC FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" ) -include(FetchContent) -include(GNUInstallDirs) +set_target_properties( + beman.cache_latest + PROPERTIES VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL} +) -if(BEMAN_CACHE_LATEST_BUILD_TESTS) - # Fetch GoogleTest - FetchContent_Declare( - GTest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG - 6910c9d9165801d8827d628cb72eb7ea9dd538c5 # release-1.16.0 - EXCLUDE_FROM_ALL - ) - set(INSTALL_GTEST OFF) # Disable GoogleTest installation - FetchContent_MakeAvailable(GTest) -endif() +add_subdirectory(include/beman/cache_latest) -add_subdirectory(src/beman/cache_latest) +beman_install_library(beman.cache_latest TARGETS beman.cache_latest) +configure_build_telemetry() if(BEMAN_CACHE_LATEST_BUILD_TESTS) + enable_testing() add_subdirectory(tests/beman/cache_latest) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..88d297c --- /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=23 \ + # 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.cache_latest 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=23 \ + -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_CACHE_LATEST`. +You can see the list of available options with: + +```bash +cmake -LH -S . -B build | grep "BEMAN_CACHE_LATEST" -C 2 +``` + +
+ +Some project-specific configure arguments + +### `BEMAN_CACHE_LATEST_BUILD_TESTS` + +Enable building tests and test infrastructure. Default: `ON`. +Values: `{ ON, OFF }`. + +### `BEMAN_CACHE_LATEST_BUILD_EXAMPLES` + +Enable building examples. Default: `ON`. Values: `{ ON, OFF }`. + +### `BEMAN_CACHE_LATEST_INSTALL_CONFIG_FILE_PACKAGE` + +Enable installing the CMake config file package. Default: `ON`. +Values: `{ ON, OFF }`. + +This is required so that users of `beman.cache_latest` can use +`find_package(beman.cache_latest)` to locate the library. + +
diff --git a/README.md b/README.md index 6619d4a..2eb4204 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/bemanproject/cache_latest/actions/workflows/ci_tests.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/bemanproject/cache_latest/badge.svg?branch=main)](https://coveralls.io/github/bemanproject/cache_latest?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp26.svg) +![Library Status](https://raw.githubusercontent.com/bemanproject/beman/refs/heads/main/images/badges/beman_badge-beman_library_under_development.svg) ![Continuous Integration Tests](https://github.com/bemanproject/cache_latest/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/bemanproject/cache_latest/actions/workflows/pre-commit-check.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/bemanproject/cache_latest/badge.svg?branch=main)](https://coveralls.io/github/bemanproject/cache_latest?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp26.svg) `beman.cache_latest` is a C++ ranges adaptor that caches the result of the last dereference of the underlying iterator. The reason for doing this is efficiency - specifically avoiding extra iterator dereferences. @@ -18,12 +18,16 @@ The library conforms to [The Beman Standard](https://github.com/bemanproject/bem **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.cache_latest` is licensed under the Apache License v2.0 with LLVM Exceptions. + ## Usage The following code snippet illustrates using `beman::cache_latest`: ```cpp -#include +#include int main() { @@ -47,21 +51,20 @@ int main() ``` +Full runnable examples can be found in [`examples/`](examples/). + ## Dependencies ### Build Environment This project requires at least the following to build: -* C++23 -* CMake 3.28 +* A C++ compiler that conforms to the C++23 standard or greater +* CMake 3.30 or later +* (Test Only) GoogleTest -This project pulls [Google Test](https://github.com/google/googletest) -from GitHub as a development dependency for its testing framework, -thus requiring an active internet connection to configure. -You can disable this behavior by setting cmake option -[`BEMAN_CACHE_LATEST_BUILD_TESTS`](#beman_cache_latest_build_tests) to `OFF` -when configuring the project. +You can disable building tests by setting CMake option `BEMAN_CACHE_LATEST_BUILD_TESTS` to +`OFF` when configuring the project. ### Supported Platforms @@ -72,217 +75,92 @@ when configuring the project. ## Development -### Develop using GitHub Codespace - -This project supports [GitHub Codespace](https://github.com/features/codespaces) -via [Development Containers](https://containers.dev/), -which allows rapid development and instant hacking in your browser. -We recommend you using GitHub codespace to explore this project as this -requires minimal setup. - -You can create a codespace for this project by clicking this badge: - -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/bemanproject/cache_latest) - -For more detailed documentation regarding creating and developing inside of -GitHub codespaces, please reference [this doc](https://docs.github.com/en/codespaces/). - -> [!NOTE] -> -> The codespace container may take up to 5 minutes to build and spin-up, -> this is normal as we need to build a custom docker container to setup -> an environment appropriate for beman projects. - -### Develop locally on your machines - -
- For Linux based systems - -Beman libraries requires [recent versions of CMake](#build-environment), -we advice you download CMake directly from [CMake's website](https://cmake.org/download/) -or install via the [Kitware apt library](https://apt.kitware.com/). - -A [supported compiler](#supported-platforms) should be available from your package manager. -Alternatively you could use an install script from official compiler vendors. - -Here is an example of how to install the latest stable version of clang -as per [the official LLVM install guide](https://apt.llvm.org/). - -```bash -bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" -``` - -
+See the [Contributing Guidelines](CONTRIBUTING.md). -
- 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. - -```bash -brew install cmake -``` +## Integrate beman.cache_latest into your project -A [supported compiler](#supported-platforms) is also available from brew. +### Build -For example, you can install latest major release of Clang++ compiler as: +You can build cache_latest using a CMake workflow preset: ```bash -brew install llvm -``` - -
- -### Configure and Build the Project Using CMake Presets - -This project recommends using [CMake Presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) -to configure, build and test the project. -Appropriate presets for major compilers have been included by default. -You can use `cmake --list-presets` to see all available presets. - -Here is an example to invoke the `gcc-debug` preset. - -```shell -cmake --workflow --preset gcc-debug +cmake --workflow --preset gcc-release ``` -Generally, there's two kinds of presets, `debug` and `release`. - -The `debug` presets are designed to aid development, so it has debugging -instrumentation enabled and as many sanitizers turned on as possible. - -> [!NOTE] -> -> The set of sanitizer supports are different across compilers. -> You can checkout the exact set compiler arguments by looking at the toolchain -> files under the [`cmake`](cmake/) directory. - -The `release` presets are designed for use in production environments, -thus it has the highest optimization turned on (e.g. `O3`). - -### Configure and Build Manually - -While [CMake Presets](#configure-and-build-the-project-using-cmake-presets) are -convenient, you might want to set different configuration or compiler arguments -than any provided preset supports. - -To configure, build and test the project with extra arguments, -you can run this sets of command. +To list available workflow presets, you can invoke: ```bash -cmake -B build -S . -DCMAKE_CXX_STANDARD=20 # Your extra arguments here. -cmake --build build -ctest --test-dir build +cmake --list-presets=workflow ``` -> [!IMPORTANT] -> -> Beman projects are -> [passive projects](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md#cmake), -> therefore, -> you will need to specify C++ version via `CMAKE_CXX_STANDARD` -> when manually configuring the project. - -### Project specific configure arguments +For details on building beman.cache_latest without using a CMake preset, refer to the +[Contributing Guidelines](CONTRIBUTING.md). -When configuring the project manually, -you can pass an array of project specific CMake configs to customize your build. +### Installation -Project specific options are prefixed with `BEMAN_CACHE_LATEST`. -You can see the list of available options with: +To install beman.cache_latest globally after building with the `gcc-release` preset, you can +run: ```bash -cmake -LH | grep "BEMAN_CACHE_LATEST" -C 2 +sudo cmake --install build/gcc-release ``` -
- - Details of CMake arguments. - -#### `BEMAN_CACHE_LATEST_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: +Alternatively, to install to a prefix, for example `/opt/beman`, you can run: ```bash -cmake -B build -S . -DCMAKE_CXX_STANDARD=20 -DBEMAN_CACHE_LATEST_BUILD_TESTS=OFF +sudo cmake --install build/gcc-release --prefix /opt/beman ``` -> [!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_CACHE_LATEST_BUILD_EXAMPLES` - -Enable building examples. Default: ON. Values: { ON, OFF }. - -
- -## Integrate beman.cache_latest into your project +This will generate the following directory structure: -To use `beman.cache_latest` in your C++ project, -include an appropriate `beman.cache_latest` header from your source code. - -```c++ -#include +```txt +/opt/beman +├── include +│ └── beman +│ └── cache_latest +│ ├── cache_latest.hpp +│ └── ... +└── lib + └── cmake + └── beman.cache_latest + ├── beman.cache_latest-config-version.cmake + ├── beman.cache_latest-config.cmake + └── beman.cache_latest-targets.cmake ``` -> [!NOTE] -> -> `beman.cache_latest` headers are to be included with the `beman/cache_latest/` 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.cache_latest` will depend on your build system. -CMake instructions are provided in following sections. +### CMake Configuration -### Linking your project to beman.cache_latest with CMake +If you installed beman.cache_latest to a prefix, you can specify that prefix to your CMake +project using `CMAKE_PREFIX_PATH`; for example, `-DCMAKE_PREFIX_PATH=/opt/beman`. -For CMake based projects, -you will need to use the `beman.cache_latest` CMake module -to define the `beman::cache_latest` CMake target: +You need to bring in the `beman.cache_latest` package to define the `beman::cache_latest` CMake +target: ```cmake find_package(beman.cache_latest REQUIRED) ``` -You will also need to add `beman::cache_latest` to the link libraries of -any libraries or executables that include beman.cache_latest's header file. +You will then need to add `beman::cache_latest` to the link libraries of any libraries or +executables that include `beman.cache_latest` headers. ```cmake target_link_libraries(yourlib PUBLIC beman::cache_latest) ``` -### Produce beman.cache_latest static library locally - -You can include cache_latest's headers locally -by producing a static `libbeman.cache_latest.a` library. - -```bash -cmake --workflow --preset gcc-release -cmake --install build/gcc-release --prefix /opt/beman.cache_latest -``` +### Using beman.cache_latest -This will generate such directory structure at `/opt/beman.cache_latest`. +To use `beman.cache_latest` in your C++ project, +include an appropriate `beman.cache_latest` header from your source code. -```txt -/opt/beman.cache_latest -├── include -│ └── beman -│ └── cache_latest -│ └── identity.hpp -└── lib - └── libbeman.cache_latest.a +```c++ +#include ``` -## License - -beman.cache_latest is licensed under the Apache License v2.0 with LLVM Exceptions. +> [!NOTE] +> +> `beman.cache_latest` headers are to be included with the `beman/cache_latest/` prefix. +> Altering include search paths to spell the include target another way (e.g. +> `#include `) is unsupported. ## Contributing diff --git a/cmake/beman.cache_last-config.cmake.in b/cmake/beman.cache_last-config.cmake.in deleted file mode 100644 index 98d849f..0000000 --- a/cmake/beman.cache_last-config.cmake.in +++ /dev/null @@ -1,9 +0,0 @@ - - -set(BEMAN_CACHE_LAST_VERSION @PROJECT_VERSION@) - -@PACKAGE_INIT@ - -include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) - -check_required_components(@PROJECT_NAME@) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5211b8f..b94568c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,7 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception set(ALL_EXAMPLES identity_direct_usage) - message("Examples to be built: ${ALL_EXAMPLES}") foreach(example ${ALL_EXAMPLES}) diff --git a/examples/identity_as_default_projection.cpp b/examples/identity_as_default_projection.cpp deleted file mode 100644 index eecabac..0000000 --- a/examples/identity_as_default_projection.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -// This example demonstrates the usage of beman::exemplar::identity as a default projection in a range-printer. -// Requires: range support (C++20) and std::identity support (C++20). -// TODO Darius: Do we need to selectively compile this example? -// Or should we assume that this project is compiled with C++20 support only? - -#include - -#include -#include // std::identity -#include -#include -#include - -namespace exe = beman::exemplar; - -// Class with a pair of values. -struct Pair { - int n; - std::string s; - - // Output the pair in the form {n, s}. - // Used by the range-printer if no custom projection is provided (default: identity projection). - friend std::ostream& operator<<(std::ostream& os, const Pair& p) { - return os << "Pair" << '{' << p.n << ", " << p.s << '}'; - } -}; - -// A range-printer that can print projected (modified) elements of a range. -// All the elements of the range are printed in the form {element1, element2, ...}. -// e.g., pairs with identity: Pair{1, one}, Pair{2, two}, Pair{3, three} -// e.g., pairs with custom projection: {1:one, 2:two, 3:three} -template -void print_helper(const std::string_view rem, R&& range, Projection projection) { - std::cout << rem << '{'; - std::ranges::for_each(range, [O = 0](const auto& o) mutable { std::cout << (O++ ? ", " : "") << o; }, projection); - std::cout << "}\n"; -}; - -// Print wrapper with exe::identity. -template // <- Notice the default projection. -void print_beman(const std::string_view rem, R&& range, Projection projection = {}) { - print_helper(rem, range, projection); -} - -// Print wrapper with std::identity. -template // <- Notice the default projection. -void print_std(const std::string_view rem, R&& range, Projection projection = {}) { - print_helper(rem, range, projection); -} - -int main() { - // A vector of pairs to print. - const std::vector pairs = { - {1, "one"}, - {2, "two"}, - {3, "three"}, - }; - - // Print the pairs using the default projection. - std::cout << "Default projection:\n"; - print_beman("\tpairs with beman: ", pairs); - print_std("\tpairs with std: ", pairs); - - // Print the pairs using a custom projection. - std::cout << "Custom projection:\n"; - print_beman("\tpairs with beman: ", pairs, [](const auto& p) { return std::to_string(p.n) + ':' + p.s; }); - print_std("\tpairs with std: ", pairs, [](const auto& p) { return std::to_string(p.n) + ':' + p.s; }); - - return 0; -} diff --git a/include/beman/cache_latest/CMakeLists.txt b/include/beman/cache_latest/CMakeLists.txt new file mode 100644 index 0000000..b61f48f --- /dev/null +++ b/include/beman/cache_latest/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +target_sources( + beman.cache_latest + PUBLIC FILE_SET HEADERS FILES cache_latest.hpp +) diff --git a/infra/.beman_submodule b/infra/.beman_submodule index bfed167..56dbbcc 100644 --- a/infra/.beman_submodule +++ b/infra/.beman_submodule @@ -1,3 +1,3 @@ [beman_submodule] remote=https://github.com/bemanproject/infra.git -commit_hash=bb58b2a1cc894d58a55bf745be78f5d27029e245 +commit_hash=ea3ef79f77fdcc378149ebc7406e81e9ceb04146 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..8052e18 100644 --- a/infra/.pre-commit-config.yaml +++ b/infra/.pre-commit-config.yaml @@ -8,25 +8,14 @@ repos: - id: check-added-large-files - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell # CMake linting and formatting - - repo: https://github.com/BlankSpruce/gersemi - rev: 0.22.3 + - repo: https://github.com/BlankSpruce/gersemi-pre-commit + rev: 0.27.2 hooks: - id: gersemi name: CMake linting exclude: ^.*/tests/.*/data/ # Exclude test data directories - - # 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..bf9bbb0 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 @@ -53,3 +52,37 @@ Some options for the project and target will also be supported: * `BEMAN_INSTALL_CONFIG_FILE_PACKAGES` - a list of package names (e.g., `beman.something`) for which to install the config file (default: all packages) * `_INSTALL_CONFIG_FILE_PACKAGE` - a per-project option to enable/disable config file installation (default: `ON` if the project is top-level, `OFF` otherwise). For instance for `beman.something`, the option would be `BEMAN_SOMETHING_INSTALL_CONFIG_FILE_PACKAGE`. + +# BuildTelemetry + +The cmake modules in this library provide access to CMake instrumentation data in Google Trace format which is visualizable with chrome://tracing and https://ui.perfetto.dev. + +Telemetry may be enabled in several ways: + +## `include` + +```cmake +include (infra/cmake/BuildTelemetry.cmake) +configure_build_telemetry() +``` + +## `find_package` + +```cmake +find_package(BuildTelemetry) +configure_build_telemetry() +``` + +as long as [BuildTelemetryConfig.cmake](./cmake/BuildTelemetryConfig.cmake) is in your module path. + +## `CMAKE_PROJECT_TOP_LEVEL_INCLUDES` +A non-invasive way to inject this telemetry into a CMake build you do not want to modify. +Add: +```sh +-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=infra/cmake/BuildTelemetry.cmake +``` +To the cmake invocation. + +In any form, CMake will call `telemetry.sh` which will copy the trace data in json format into a `.trace` subdirectory within the build directory. + +Multiple calls to `configure_build_telemetry` will only configure the callback hooks once, so it is safe to enable multiple times, including by TOP_LEVEL_INCLUDE. diff --git a/infra/cmake/BuildTelemetry.cmake b/infra/cmake/BuildTelemetry.cmake new file mode 100755 index 0000000..c2ff343 --- /dev/null +++ b/infra/cmake/BuildTelemetry.cmake @@ -0,0 +1,4 @@ +include_guard(GLOBAL) + +include(${CMAKE_CURRENT_LIST_DIR}/BuildTelemetryConfig.cmake) +configure_build_telemetry() diff --git a/infra/cmake/BuildTelemetryConfig.cmake b/infra/cmake/BuildTelemetryConfig.cmake new file mode 100755 index 0000000..15aae48 --- /dev/null +++ b/infra/cmake/BuildTelemetryConfig.cmake @@ -0,0 +1,58 @@ +include_guard(GLOBAL) + +set(BUILD_TELEMETRY_DIR ${CMAKE_CURRENT_LIST_DIR}) + +function(configure_build_telemetry) + if(NOT BUILD_TELEMETRY_CONFIGURATION) + # Check if the CMake version is at least 4.3 + if(CMAKE_VERSION VERSION_LESS "4.3") + message( + STATUS + "CMake version is less than 4.3, configuring cmake_instrumentation is unavailable." + ) + return() + else() + message(STATUS "Configuring Build Telemetry") + endif() + + # Find bash and jq for the telemetry callback script. + # On Windows, Git for Windows provides bash if available. + find_program(BEMAN_BASH bash) + find_program(BEMAN_JQ jq) + if(NOT BEMAN_BASH OR NOT BEMAN_JQ) + message( + STATUS + "bash or jq not found, build telemetry disabled on this platform." + ) + return() + endif() + + # Telemetry query + cmake_instrumentation( + API_VERSION 1 + DATA_VERSION 1 + OPTIONS staticSystemInformation dynamicSystemInformation trace + HOOKS + postGenerate + preBuild + postBuild + preCMakeBuild + postCMakeBuild + postCMakeInstall + postCTest + CALLBACK ${BEMAN_BASH} + ${BUILD_TELEMETRY_DIR}/telemetry.sh + ) + message( + DEBUG + "using callback script ${BUILD_TELEMETRY_DIR}/telemetry.sh via ${BEMAN_BASH}" + ) + + # Mark configuration as done in cache + set(BUILD_TELEMETRY_CONFIGURATION + TRUE + CACHE INTERNAL + "Flag to ensure Build Telemetry configured only once" + ) + endif() +endfunction(configure_build_telemetry) diff --git a/infra/cmake/Config.cmake.in b/infra/cmake/Config.cmake.in new file mode 100644 index 0000000..3f1341c --- /dev/null +++ b/infra/cmake/Config.cmake.in @@ -0,0 +1,12 @@ +# cmake/Config.cmake.in -*-makefile-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include(CMakeFindDependencyMacro) + +@BEMAN_INSTALL_FIND_DEPENDENCIES@ + +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@BEMAN_INSTALL_BASE_PKG_NAME@-targets.cmake) + +check_required_components(@BEMAN_INSTALL_BASE_PKG_NAME@) diff --git a/infra/cmake/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..dc5a4d1 --- /dev/null +++ b/infra/cmake/beman-install-library.cmake @@ -0,0 +1,323 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include_guard(GLOBAL) + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +# beman_install_library +# ===================== +# +# Installs a library (or set of targets) along with headers, C++ modules, +# and optional CMake package configuration files. +# +# Usage: +# ------ +# beman_install_library( +# TARGETS [ ...] +# [DEPENDENCIES [ ...]] +# [NAMESPACE ] +# [EXPORT_NAME ] +# [DESTINATION ] +# ) +# +# Arguments: +# ---------- +# +# name +# Logical package name (e.g. "beman.utility"). +# Used to derive config file names and cache variable prefixes. +# +# TARGETS (required) +# List of CMake targets to install. +# +# DEPENDENCIES (optional) +# Semicolon-separated list, one dependency per entry. +# Each entry is a valid find_dependency() argument list. +# Note: you must use the bracket form for quoting if not only a package name is used! +# "[===[beman.inplace_vector 1.0.0]===] [===[beman.scope 0.0.1 EXACT]===] fmt" +# +# NAMESPACE (optional) +# Namespace for exported targets. +# Defaults to "beman::". +# +# EXPORT_NAME (optional) +# Name of the CMake export set. +# Defaults to "-targets". +# +# DESTINATION (optional) +# The install destination for CXX_MODULES. +# Defaults to ${CMAKE_INSTALL_LIBDIR}/cmake/${name}/modules. +# +# Brief +# ----- +# +# This function installs the specified project TARGETS and its FILE_SET +# HEADERS to the default CMAKE install destination. +# +# It also handles the installation of the CMake config package files if +# needed. If the given targets has a PUBLIC FILE_SET CXX_MODULE, it will also +# installed to the given DESTINATION +# +# Cache variables: +# ---------------- +# +# BEMAN_INSTALL_CONFIG_FILE_PACKAGES +# List of package names for which config files should be installed. +# +# _INSTALL_CONFIG_FILE_PACKAGE +# Per-package override to enable/disable config file installation. +# is the uppercased package name with dots replaced by underscores. +# +# Caveats +# ------- +# +# **Only one `PUBLIC FILE_SET CXX_MODULES` is yet supported to install with this +# function!** +# +# **Only header files contained in a `PUBLIC FILE_SET TYPE HEADERS` will be +# install with this function!** + +function(beman_install_library name) + # ---------------------------- + # Argument parsing + # ---------------------------- + set(oneValueArgs NAMESPACE EXPORT_NAME DESTINATION) + set(multiValueArgs TARGETS DEPENDENCIES) + + cmake_parse_arguments( + BEMAN_INSTALL + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + if(NOT BEMAN_INSTALL_TARGETS) + message( + FATAL_ERROR + "beman_install_library(${name}): TARGETS must be specified" + ) + endif() + + if(CMAKE_SKIP_INSTALL_RULES) + message( + WARNING + "beman_install_library(${name}): not installing targets '${BEMAN_INSTALL_TARGETS}' due to CMAKE_SKIP_INSTALL_RULES" + ) + return() + endif() + + set(_config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${name}") + + # ---------------------------- + # Defaults + # ---------------------------- + if(NOT BEMAN_INSTALL_NAMESPACE) + set(BEMAN_INSTALL_NAMESPACE "beman::") + endif() + + if(NOT BEMAN_INSTALL_EXPORT_NAME) + set(BEMAN_INSTALL_EXPORT_NAME "${name}-targets") + endif() + + if(NOT BEMAN_INSTALL_DESTINATION) + set(BEMAN_INSTALL_DESTINATION "${_config_install_dir}/modules") + endif() + + string(REPLACE "beman." "" install_component_name "${name}") + message( + VERBOSE + "beman-install-library(${name}): COMPONENT '${install_component_name}'" + ) + + # -------------------------------------------------- + # Install each target with all of its file sets + # -------------------------------------------------- + foreach(_tgt IN LISTS BEMAN_INSTALL_TARGETS) + if(NOT TARGET "${_tgt}") + message( + WARNING + "beman_install_library(${name}): '${_tgt}' is not a target" + ) + continue() + endif() + + # Given foo.bar, the component name is bar + string(REPLACE "." ";" name_parts "${_tgt}") + # fail if the name doesn't look like foo.bar + list(LENGTH name_parts name_parts_length) + if(NOT name_parts_length EQUAL 2) + message( + FATAL_ERROR + "beman_install_library(${name}): expects a name of the form 'beman.', got '${_tgt}'" + ) + endif() + list(GET name_parts -1 component_name) + set_target_properties( + "${_tgt}" + PROPERTIES EXPORT_NAME "${component_name}" + ) + message( + VERBOSE + "beman_install_library(${name}): EXPORT_NAME ${component_name} for TARGET '${_tgt}'" + ) + + # Get the list of interface header sets, exact one expected! + set(_install_header_set_args) + get_target_property( + _available_header_sets + ${_tgt} + INTERFACE_HEADER_SETS + ) + if(_available_header_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has INTERFACE_HEADER_SETS=${_available_header_sets}" + ) + foreach(_install_header_set IN LISTS _available_header_sets) + list( + APPEND _install_header_set_args + FILE_SET + "${_install_header_set}" + COMPONENT + "${install_component_name}_Development" + ) + endforeach() + else() + set(_install_header_set_args FILE_SET HEADERS) # Note: empty FILE_SET in this case! CK + endif() + + # Detect presence of PUBLIC C++ module file sets. Note: exact one is expected! + get_target_property(_module_sets "${_tgt}" INTERFACE_CXX_MODULE_SETS) + if(_module_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has INTERFACE_CXX_MODULE_SETS=${_module_sets}" + ) + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_INSTALL_EXPORT_NAME} + ARCHIVE COMPONENT "${install_component_name}_Development" + LIBRARY + COMPONENT "${install_component_name}_Runtime" + NAMELINK_COMPONENT "${install_component_name}_Development" + RUNTIME COMPONENT "${install_component_name}_Runtime" + ${_install_header_set_args} + FILE_SET ${_module_sets} + DESTINATION "${BEMAN_INSTALL_DESTINATION}" + COMPONENT "${install_component_name}_Development" + # NOTE: There's currently no convention for this location! CK + CXX_MODULES_BMI + DESTINATION + ${_config_install_dir}/bmi-${CMAKE_CXX_COMPILER_ID}_$ + COMPONENT "${install_component_name}_Development" + ) + else() + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_INSTALL_EXPORT_NAME} + ARCHIVE COMPONENT "${install_component_name}_Development" + LIBRARY + COMPONENT "${install_component_name}_Runtime" + NAMELINK_COMPONENT "${install_component_name}_Development" + RUNTIME COMPONENT "${install_component_name}_Runtime" + ${_install_header_set_args} + ) + endif() + endforeach() + + # -------------------------------------------------- + # Export targets + # -------------------------------------------------- + # gersemi: off + install( + EXPORT ${BEMAN_INSTALL_EXPORT_NAME} + NAMESPACE ${BEMAN_INSTALL_NAMESPACE} + CXX_MODULES_DIRECTORY cxx-modules + DESTINATION ${_config_install_dir} + COMPONENT "${install_component_name}_Development" + ) + # gersemi: on + + # ---------------------------------------- + # Config file installation logic + # + # Precedence (highest to lowest): + # 1. Per-package variable _INSTALL_CONFIG_FILE_PACKAGE + # 2. Allow-list BEMAN_INSTALL_CONFIG_FILE_PACKAGES (if defined) + # 3. Default: ON + # ---------------------------------------- + string(TOUPPER "${name}" _pkg_upper) + string(REPLACE "." "_" _pkg_prefix "${_pkg_upper}") + + option( + ${_pkg_prefix}_INSTALL_CONFIG_FILE_PACKAGE + "Enable creating and installing a CMake config-file package. Default: ON. Values: { ON, OFF }." + ON + ) + + set(_pkg_var "${_pkg_prefix}_INSTALL_CONFIG_FILE_PACKAGE") + + # Default: install config files + set(_install_config ON) + + # If the allow-list is defined, only install for packages in the list + if(DEFINED BEMAN_INSTALL_CONFIG_FILE_PACKAGES) + if(NOT "${name}" IN_LIST BEMAN_INSTALL_CONFIG_FILE_PACKAGES) + set(_install_config OFF) + endif() + endif() + + # Per-package override takes highest precedence + if(DEFINED ${_pkg_var}) + set(_install_config ${${_pkg_var}}) + endif() + + # ---------------------------------------- + # expand dependencies + # ---------------------------------------- + set(_beman_find_deps "") + foreach(dep IN LISTS BEMAN_INSTALL_DEPENDENCIES) + message( + VERBOSE + "beman-install-library(${name}): Add find_dependency(${dep})" + ) + string(APPEND _beman_find_deps "find_dependency(${dep})\n") + endforeach() + set(BEMAN_INSTALL_FIND_DEPENDENCIES "${_beman_find_deps}") + + # ---------------------------------------- + # Generate + install config files + # ---------------------------------------- + if(_install_config) + set(BEMAN_INSTALL_BASE_PKG_NAME ${name}) + configure_package_config_file( + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake" + INSTALL_DESTINATION ${_config_install_dir} + ) + + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config-version.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion + ) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config-version.cmake" + DESTINATION ${_config_install_dir} + COMPONENT "${install_component_name}_Development" + ) + else() + message( + WARNING + "beman-install-library(${name}): Not installing a config package for '${name}'" + ) + endif() +endfunction() + +set(CPACK_GENERATOR TGZ) +include(CPack) diff --git a/infra/cmake/telemetry.sh b/infra/cmake/telemetry.sh new file mode 100755 index 0000000..307cc94 --- /dev/null +++ b/infra/cmake/telemetry.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +trap 'echo "Aborting due to errexit on line $LINENO. Exit code: $?" >&2' ERR +set -o errtrace +set -o pipefail +IFS=$'\n\t' + +############################################################################### +# Environment +############################################################################### + +# $_ME +# +# This program's basename. +_ME="$(basename "${0}")" + +############################################################################### +# Help +############################################################################### + +# _print_help() +# +# Usage: +# _print_help +# +# Print the program help information. +_print_help() { + cat <] + ${_ME} -h | --help + +Options: + -h --help Show this screen. + +Environment: + Setting DEBUG_TELEMETRY in the environment will enable DEBUG logging +HEREDOC +} + +############################################################################### +# Program Functions +############################################################################### +_debug_print() { + if [[ -n "${DEBUG_TELEMETRY:-}" ]]; then + printf "[DEBUG] $(date +'%H:%M:%S'): %s \n" "$1" >&2 + fi +} + +_check_file_exists() { + local file="$1" + if [[ ! -f "${file}" ]]; then + echo "Error: File not found: ${file}" >&2 + exit 1 # Exit the entire script with a non-zero status + fi +} + +_process_index() { + indexFile=${1:-} + _check_file_exists "${indexFile}" + _debug_print "$(cat "${indexFile}")" + + local buildDir + buildDir=$(jq -r '.buildDir' "${1:-}") + _debug_print "$(printf "buildDir is |%q|" "${buildDir}")" + + local dataDir + dataDir=$(jq -r '.dataDir' "${1:-}") + _debug_print "$(printf "dataDir is |%q|" "${dataDir}")" + + local hook + hook=$(jq -r '.hook' "${1:-}") + _debug_print "$(printf "hook is |%q|" "${hook}")" + + local trace + trace=$(jq -r '.trace' "${1:-}") + _debug_print "$(printf "trace is |%q|" "${trace}")" + + local outputDir + outputDir="${buildDir}/.trace" + _debug_print "$(printf "Copy trace to |%q|" "${outputDir}")" + mkdir -p "${outputDir}" + + local traceDestFile + traceDestFile="${outputDir}/${hook}-$(basename "${trace}")" + _debug_print "$(printf "traceDestFile: |%q|" "${traceDestFile}")" + cp "${dataDir}/${trace}" "${outputDir}/${hook}-$(basename "${trace}")" +} + +############################################################################### +# Main +############################################################################### + +# _main() +# +# Usage: +# _main [] [] +# +# Description: +# Entry point for the program, handling basic option parsing and dispatching. +_main() { + # Avoid complex option parsing when only one program option is expected. + if [[ "${1:-}" =~ ^-h|--help$ ]] + then + _print_help + else + _process_index "$@" + fi +} + +# Call `_main` after everything has been defined. +_main "$@" diff --git a/infra/cmake/use-fetch-content.cmake b/infra/cmake/use-fetch-content.cmake index 4ed4839..0564513 100644 --- a/infra/cmake/use-fetch-content.cmake +++ b/infra/cmake/use-fetch-content.cmake @@ -15,8 +15,7 @@ message(TRACE "BemanExemplar_projectDir=\"${BemanExemplar_projectDir}\"") message(TRACE "BEMAN_EXEMPLAR_LOCKFILE=\"${BEMAN_EXEMPLAR_LOCKFILE}\"") file( - REAL_PATH - "${BEMAN_EXEMPLAR_LOCKFILE}" + REAL_PATH "${BEMAN_EXEMPLAR_LOCKFILE}" BemanExemplar_lockfile BASE_DIRECTORY "${BemanExemplar_projectDir}" EXPAND_TILDE @@ -38,8 +37,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "dependencies" field and store it in BemanExemplar_dependenciesObj string( - JSON - BemanExemplar_dependenciesObj + JSON BemanExemplar_dependenciesObj ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_rootObj}" "dependencies" @@ -50,8 +48,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the length of the libraries array and store it in BemanExemplar_dependenciesObj string( - JSON - BemanExemplar_numDependencies + JSON BemanExemplar_numDependencies ERROR_VARIABLE BemanExemplar_error LENGTH "${BemanExemplar_dependenciesObj}" ) @@ -73,8 +70,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the dependency object at BemanExemplar_index # and store it in BemanExemplar_depObj string( - JSON - BemanExemplar_depObj + JSON BemanExemplar_depObj ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_dependenciesObj}" "${BemanExemplar_index}" @@ -88,8 +84,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "name" field and store it in BemanExemplar_name string( - JSON - BemanExemplar_name + JSON BemanExemplar_name ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_depObj}" "name" @@ -103,8 +98,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "package_name" field and store it in BemanExemplar_pkgName string( - JSON - BemanExemplar_pkgName + JSON BemanExemplar_pkgName ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_depObj}" "package_name" @@ -118,8 +112,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "git_repository" field and store it in BemanExemplar_repo string( - JSON - BemanExemplar_repo + JSON BemanExemplar_repo ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_depObj}" "git_repository" @@ -133,8 +126,7 @@ function(BemanExemplar_provideDependency method package_name) # Get the "git_tag" field and store it in BemanExemplar_tag string( - JSON - BemanExemplar_tag + JSON BemanExemplar_tag ERROR_VARIABLE BemanExemplar_error GET "${BemanExemplar_depObj}" "git_tag" @@ -149,14 +141,12 @@ function(BemanExemplar_provideDependency method package_name) if(method STREQUAL "FIND_PACKAGE") if(package_name STREQUAL BemanExemplar_pkgName) string( - APPEND - BemanExemplar_debug + APPEND BemanExemplar_debug "Redirecting find_package calls for ${BemanExemplar_pkgName} " "to FetchContent logic.\n" ) string( - APPEND - BemanExemplar_debug + APPEND BemanExemplar_debug "Fetching ${BemanExemplar_repo} at " "${BemanExemplar_tag} according to ${BemanExemplar_lockfile}." ) @@ -167,9 +157,63 @@ function(BemanExemplar_provideDependency method package_name) GIT_TAG "${BemanExemplar_tag}" EXCLUDE_FROM_ALL ) - set(INSTALL_GTEST OFF) # Disable GoogleTest installation + + # Apply per-dependency cmake_args from the lockfile + string( + JSON BemanExemplar_cmakeArgs + ERROR_VARIABLE BemanExemplar_cmakeArgsError + GET "${BemanExemplar_depObj}" + "cmake_args" + ) + if(NOT BemanExemplar_cmakeArgsError) + string( + JSON BemanExemplar_numCmakeArgs + LENGTH "${BemanExemplar_cmakeArgs}" + ) + if(BemanExemplar_numCmakeArgs GREATER 0) + math( + EXPR + BemanExemplar_maxArgIndex + "${BemanExemplar_numCmakeArgs} - 1" + ) + foreach( + BemanExemplar_argIndex + RANGE "${BemanExemplar_maxArgIndex}" + ) + string( + JSON BemanExemplar_argKey + MEMBER "${BemanExemplar_cmakeArgs}" + "${BemanExemplar_argIndex}" + ) + string( + JSON BemanExemplar_argValue + GET "${BemanExemplar_cmakeArgs}" + "${BemanExemplar_argKey}" + ) + message( + DEBUG + "Setting ${BemanExemplar_argKey}=${BemanExemplar_argValue} for ${BemanExemplar_name}" + ) + set("${BemanExemplar_argKey}" + "${BemanExemplar_argValue}" + ) + endforeach() + endif() + endif() + FetchContent_MakeAvailable("${BemanExemplar_name}") + # Catch2's CTest integration module isn't on CMAKE_MODULE_PATH + # when brought in via FetchContent. Add it so that + # `include(Catch)` works. + if(BemanExemplar_pkgName STREQUAL "Catch2") + list( + APPEND CMAKE_MODULE_PATH + "${${BemanExemplar_name}_SOURCE_DIR}/extras" + ) + set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) + endif() + # Important! _FOUND tells CMake that `find_package` is # not needed for this package anymore set("${BemanExemplar_pkgName}_FOUND" TRUE PARENT_SCOPE) 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/lockfile.json b/lockfile.json index 4208a98..787b905 100644 --- a/lockfile.json +++ b/lockfile.json @@ -1,3 +1,13 @@ { - "dependencies": [] + "dependencies": [ + { + "name": "googletest", + "package_name": "GTest", + "git_repository": "https://github.com/google/googletest.git", + "git_tag": "6910c9d9165801d8827d628cb72eb7ea9dd538c5", + "cmake_args": { + "INSTALL_GTEST": "OFF" + } + } + ] } diff --git a/src/beman/cache_latest/CMakeLists.txt b/src/beman/cache_latest/CMakeLists.txt deleted file mode 100644 index 45c68f0..0000000 --- a/src/beman/cache_latest/CMakeLists.txt +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -add_library(beman.cache_latest) -add_library(beman::cache_latest ALIAS beman.cache_latest) - -target_sources(beman.cache_latest PRIVATE cache_latest.cpp) - -target_sources( - beman.cache_latest - PUBLIC - FILE_SET HEADERS - BASE_DIRS ${PROJECT_SOURCE_DIR}/include - FILES - ${PROJECT_SOURCE_DIR}/include/beman/cache_latest/cache_latest.hpp -) - -set_target_properties( - beman.cache_latest - PROPERTIES VERIFY_INTERFACE_HEADER_SETS ON EXPORT_NAME cache_latest -) - -install( - TARGETS beman.cache_latest - EXPORT beman.cache_latest - DESTINATION ${CMAKE_INSTALL_LIBDIR}$<$:/debug> - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}$<$:/debug> - FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) - -if(BEMAN_cache_latest_INSTALL_CONFIG_FILE_PACKAGE) - include(CMakePackageConfigHelpers) - - configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake.in" - "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" - INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" - PATH_VARS PROJECT_NAME PROJECT_VERSION - ) - - write_basic_package_version_file( - "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion - ) - - install( - FILES - "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" - "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-version.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" - COMPONENT development - ) - - install( - EXPORT beman.cache_latest - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" - NAMESPACE beman:: - FILE ${PROJECT_NAME}-targets.cmake - COMPONENT development - ) -endif() diff --git a/src/beman/cache_latest/cache_latest.cpp b/src/beman/cache_latest/cache_latest.cpp deleted file mode 100644 index df45346..0000000 --- a/src/beman/cache_latest/cache_latest.cpp +++ /dev/null @@ -1 +0,0 @@ -#include diff --git a/tests/beman/cache_latest/CMakeLists.txt b/tests/beman/cache_latest/CMakeLists.txt index 654918c..ba06e01 100644 --- a/tests/beman/cache_latest/CMakeLists.txt +++ b/tests/beman/cache_latest/CMakeLists.txt @@ -1,9 +1,8 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -include(GoogleTest) +find_package(GTest REQUIRED) add_executable(beman.cache_latest.tests.cache_latest) - target_sources( beman.cache_latest.tests.cache_latest PRIVATE cache_latest.test.cpp @@ -13,4 +12,5 @@ target_link_libraries( PRIVATE beman::cache_latest GTest::gtest GTest::gtest_main ) -gtest_add_tests(beman.cache_latest.tests.cache_latest "" AUTO) +include(GoogleTest) +gtest_discover_tests(beman.cache_latest.tests.cache_latest)