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
-->
-  [](https://coveralls.io/github/bemanproject/cache_latest?branch=main) 
+   [](https://coveralls.io/github/bemanproject/cache_latest?branch=main) 
`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:
-
-[](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)