Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4e5aeaa
Migrate template from cookiecutter to Copier
steve-downey May 5, 2026
ca64c2a
Document the Copier template workflow
steve-downey May 5, 2026
5275987
Add Copier migration support
steve-downey May 8, 2026
7140661
Merge upstream/main into copier to repair PR
steve-downey May 17, 2026
a141226
Remove merge artifact
steve-downey May 18, 2026
8d03085
Revert some white space changes in infra/
steve-downey May 18, 2026
896e55d
Whitespace in copier templates
steve-downey May 18, 2026
c3520e6
remove template for answers
steve-downey May 21, 2026
b509aa8
fix template answers
steve-downey May 21, 2026
e12e9d5
put conditional answers into the answers file
steve-downey May 21, 2026
a8a22b1
fix defaults to persist
steve-downey May 22, 2026
debcfd4
Randomize the cron settings once
steve-downey May 22, 2026
7f9055c
Update README and stamp.sh
steve-downey May 22, 2026
36573cf
Update templates to pass check-copier
steve-downey May 22, 2026
73d02de
Update infra submodule and deploy copier update scripts
steve-downey May 23, 2026
45edf68
Test all template matrix variants in CI cleanly
steve-downey May 23, 2026
dab6527
Add MAINTAINERS documentation for template pipeline
steve-downey May 23, 2026
5abb32b
Sync copier-test job into jinja template
steve-downey May 23, 2026
de7a141
Support MSVC Modules on CMake 4.3 runners
steve-downey May 23, 2026
5713851
Switch copier-test compiler vector to gcc-release to resolve std modu…
steve-downey May 23, 2026
325a171
Autoformat MAINTAINERS.md trailing whitespace
steve-downey May 23, 2026
a0a4ebe
Refactor verbose CMake patch matching to concise version blocks
steve-downey May 23, 2026
837b856
Add CMake matrix CI evaluation step executing via test loop
steve-downey May 23, 2026
f8eb0b3
Propagate matrix to CI
steve-downey May 23, 2026
139542e
Apply pre-commit formatting fixes
steve-downey May 23, 2026
3520b79
Fix uv pip install flag
steve-downey May 23, 2026
7862bb8
Fix uv proxy and sync templates to fix GH Actions matrix failures
steve-downey May 23, 2026
c48759b
Update infra submodule hash to match upstream and fix template EOF/Li…
steve-downey May 23, 2026
5d69995
Fallback infra submodule to local steve-downey fork temporarily to fi…
steve-downey May 23, 2026
c3c230c
Repoint submodule to upstream
steve-downey May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
infra/** linguist-vendored
cookiecutter/** linguist-vendored
template/** linguist-vendored
copier/** linguist-vendored
*.bib -linguist-detectable
*.tex -linguist-detectable
papers/* linguist-documentation
12 changes: 6 additions & 6 deletions .github/workflows/catch2_exemplar_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ jobs:
uses: actions/checkout@v6
- name: Test catch2 exemplar
run: |
cd cookiecutter
source ./check_cookiecutter.sh
cookiecutter_venv_path=$(mktemp --directory --dry-run)
setup_venv "$cookiecutter_venv_path"
stamp "$PWD" "./catch2_exemplar" "catch2" "true"
cd catch2_exemplar/exemplar
cd copier
source ./check_copier.sh
copier_venv_path=$(mktemp --directory --dry-run)
setup_venv "$copier_venv_path"
stamp "$repo_root" "./catch2_exemplar" "catch2" "true"
cd catch2_exemplar
cmake -B build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake -DCMAKE_CXX_STANDARD=20 -DCMAKE_INSTALL_PREFIX=$PWD/dist
cmake --build build
ctest --test-dir build
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/ci_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,45 @@ jobs:
beman-submodule-check:
uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.7.2

copier-test:
runs-on: ubuntu-latest
container:
image: ghcr.io/bemanproject/infra-containers-gcc:latest
steps:
- uses: actions/checkout@v4
- name: Install uv
shell: bash
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Test Copier Template Variants
shell: bash
env:
GITHUB_ACTIONS: true
# gcc-release will use GCC 15 which supports C++23 modules appropriately in this container
run: ./copier/test_standard_project.sh gcc-release

copier-cmake-matrix:
runs-on: ubuntu-latest
container:
image: ghcr.io/bemanproject/infra-containers-gcc:latest
strategy:
fail-fast: false
matrix:
cmake_version: ["3.30.9", "3.31.10", "4.0.3", "4.1.3", "4.2.3", "4.3.2"]
steps:
- uses: actions/checkout@v4
- name: Install uv
shell: bash
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Test Copier Template Variants Matrix
shell: bash
env:
GITHUB_ACTIONS: true
# Run across our experimental boundaries
run: ./copier/test_cmake_matrix.sh gcc-release ${{ matrix.cmake_version }}
preset-test:
uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.7.2
with:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

name: Cookiecutter Test
name: Copier Test
on:
push:
branches:
- main
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
cookiecutter-test:
copier-test:
runs-on: ubuntu-latest
name: "Check cookiecutter for consistency"
name: "Check copier for consistency"
steps:
- name: Checkout
uses: actions/checkout@v6
- name: beman cookiecutter consistency check
- name: beman copier consistency check
run: |
./cookiecutter/check_cookiecutter.sh
./copier/check_copier.sh
12 changes: 6 additions & 6 deletions .github/workflows/todo_exemplar_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ jobs:
uses: actions/checkout@v6
- name: Test static exemplar
run: |
cd cookiecutter
source ./check_cookiecutter.sh
cookiecutter_venv_path=$(mktemp --directory --dry-run)
setup_venv "$cookiecutter_venv_path"
stamp "$PWD" "./static_exemplar" "gtest" "false"
cd static_exemplar/exemplar
cd copier
source ./check_copier.sh
copier_venv_path=$(mktemp --directory --dry-run)
setup_venv "$copier_venv_path"
stamp "$repo_root" "./static_exemplar" "gtest" "false"
cd static_exemplar
grep -r 'identity' . && exit 1
find . -name '*identity*' | grep . && exit 1
cmake -B build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake -DCMAKE_CXX_STANDARD=20 -DCMAKE_INSTALL_PREFIX=$PWD/dist
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ repos:
hooks:
- id: codespell

exclude: 'cookiecutter/|infra/|port/'
exclude: 'template/|copier/|infra/|port/'
41 changes: 41 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,47 @@ ctest --test-dir build
The file `./lockfile.json` configures the list of dependencies and versions that will be
acquired by FetchContent.

## Developing The Copier Template

This repository doubles as both the exemplar library and the Copier template used to stamp
new Beman libraries.

The template workflow is organized as follows:

* `copier.yml` defines the Copier questions, defaults, validation rules, and post-copy
tasks.
* `template/` contains the files rendered into generated projects.
* `stamp.sh` is the user-facing wrapper for stamping a forked exemplar repository into a
new project.
* `./copier/check_copier.sh` validates that exemplar regenerates cleanly and that a
non-exemplar project does not leak exemplar-specific names like `identity`.

When testing local template changes, do not render directly from the Git checkout. The
Copier helper scripts intentionally render from a temporary snapshot with `.git`, `build`,
and `.venv` excluded so that validation uses the current worktree contents.

Run the template checks with:

```shell
./copier/check_copier.sh
```

If you need to smoke-test generated projects manually, start from the same validated flow:

```shell
tmpdir=$(mktemp -d)
python3 -m venv "$tmpdir/venv"
"$tmpdir/venv/bin/python3" -m pip install copier
src=$(mktemp -d)
rsync -a --exclude .git --exclude build --exclude .venv ./ "$src/"
"$tmpdir/venv/bin/copier" copy --trust --defaults \
-d template_src_path=https://github.com/bemanproject/exemplar.git \
-d template_commit="$(git rev-parse HEAD)" \
"$src" "$tmpdir/project"
```

Then configure and build the generated project as usual.

## Project-specific configure arguments

Project-specific options are prefixed with `BEMAN_EXEMPLAR`.
Expand Down
177 changes: 143 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,163 @@
# How to Use This Template

To create a new Beman library, first click the "Use this template" dropdown in the
top-right and select "Create a new repository":
This repository uses [Copier](https://copier.readthedocs.io/) as its templating engine to generate, manage, and update Beman library boilerplate. The template inputs are defined in `copier.yml`, and the rendered project files live under `template/`.

<table><tr><td>
<img src="images/use-this-template.png" width="400px">
</td></tr></table>
> [!NOTE]
> ?? **`stamp.sh` is deprecated.** If you are used to the legacy workflow of forking this repository and running `./stamp.sh`, please transition to the natively supported `copier` workflow below. The script remains for legacy CI compatibility but will not receive future updates.

This will create a new repository that's an exact copy of exemplar. The next step is to
customize it for your use case.
## ? Quick Start: Create a New Library

To do so, execute the bash script `stamp.sh`. This script will prompt for parameters like
the new library's name, paper number, and description. Then it will replace your exemplar
copy with a stamped-out template containing these parameters and create a corresponding
git commit and branch:
If you are already familiar with the Beman project lifecycle, you can generate a new library immediately using `uvx` (via the [uv package manager](https://docs.astral.sh/uv/)):

```bash
uvx --from copier copier copy "git+[https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git)" my-new-library
cd my-new-library
git init
git add .
git commit -m "Initial commit from Beman Exemplar template"
uvx pre-commit install
```
$ ./stamp.sh
[1/6] project_name (my_project_name): example_library
[2/6] maintainer (your_github_username): your_username
[3/6] minimum_cpp_build_version (20):
[4/6] paper (PnnnnRr): P9999R9
[5/6] description (Short project description.):
[6/6] Select unit_test_library
1 - gtest
2 - catch2
Choose from [1/2] (1):
Switched to a new branch 'stamp'
Successfully stamped out exemplar template to the new branch 'stamp'.
Try 'git push origin stamp' to push the branch upstream,
then create a pull request.

---

## ?? The Beman Project Lifecycle

To create a new library and get it officially adopted into the Beman project, you will generate the project locally, push it to your personal GitHub for active development, and eventually transfer it to the `bemanproject` organization.

> **Why this specific workflow?**
> Developing on your personal account first gives you a sandbox to experiment and clean up your Git history. Transferring the repository (rather than creating it directly in the org) ensures the `bemanproject` organization ultimately holds the canonical, unbroken Git history, which prevents downstream forks from breaking later.

### Step 1: Push to Your Personal GitHub
After running the Quick Start commands above, push the fresh template to your personal account to begin development.

**Option A: Using the GitHub CLI (`gh`)**
```bash
gh repo create my-new-library --public --source=. --remote=origin --push
```

From there, you can simply fill in all the remaining parts of the repository that are
labeled 'todo'.
**Option B: Using the GitHub Browser Interface**
1. [Create a new repository](https://github.com/new). Name it exactly what you named your local folder. Leave it completely empty.
2. Link and push your local code:
```bash
git remote add origin [https://github.com/](https://github.com/)<your-username>/my-new-library.git
git branch -M main
git push -u origin main
```

What follow is an example of a Beman library README.
### Step 2: Incubation & Implementation
Before transferring the project to the Beman organization, take the time to build out the actual proposal.
* Expand beyond the default placeholder headers (e.g., `my-new-library.hpp`).
* Stub out the necessary files for your library's domain.
* Write out the skeleton of the library's design and documentation.
* *Optional:* Once you have a working sandbox, feel free to squash or clean up your Git history using an interactive rebase (`git rebase -i`) before handing it over.

# beman.exemplar: A Beman Library Exemplar
### Step 3: Transfer to the Beman Project
Once the repository has enough substance to be reviewed or collaborated on, hand it over to the organization to establish it as the canonical source.

1. On your GitHub repository page, go to **Settings** > **General**.
2. Scroll to the bottom to the **Danger Zone** and click **Transfer ownership**.
3. Type `bemanproject` as the new owner and confirm.

### Step 4: Fork It Back for Ongoing Development
Now that `bemanproject/my-new-library` is the official upstream repository, standard open-source contribution rules apply.

1. Navigate to the new URL: `https://github.com/bemanproject/my-new-library`.
2. Click **Fork** in the top right corner to create a fork in your personal namespace.
3. Update your local repository's remotes so you can pull from upstream and push to your new fork:
```bash
# Rename your current remote (the org repo) to 'upstream'
git remote rename origin upstream

# Add your new personal fork as 'origin'
git remote add origin [https://github.com/](https://github.com/)<your-username>/my-new-library.git
```

---

## ?? Reconfiguring or Renaming Your Project

One of the biggest advantages of using `copier` is that it actively maintains your project's boilerplate. If you start out with a placeholder name and later decide to rename your library (or change your minimum C++ version), you can instruct Copier to update the structural boilerplate using the `--data` flag.

<!--
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
For example, to rename your project to `range_discombobulator`:

```bash
uvx --from copier copier update --trust --skip-answered --data project_name=range_discombobulator
```

**What this does:**
* `--data project_name=...`: Overrides your previous answer for the project name.
* `--skip-answered`: Skips the interactive prompt for all the questions you aren't changing.

Copier will calculate the difference and automatically update the template-provided files—renaming CMake configurations, GitHub Actions matrices, standard folder paths, and core macros.

?? **Important Caveat:** Copier only manages the files it originally generated. If you have added custom files (like `detail/internal.hpp`), or manually typed the old project name into new C++ namespaces or include guards, you will need to update those manually. Always review the changes with `git diff`, and use your IDE or `grep` to hunt down any lingering references to the old name before committing!

## Rebasing An Older Exemplar Clone Onto Copier

If your repository predates the Copier migration and was created as a plain GitHub template copy, bootstrap a fresh stamped baseline at the original exemplar split point, then rebase your project commits onto that baseline.

Start from a clean worktree and make sure you can identify the branch you want to migrate; the examples below assume `main`.

```shell
git remote add exemplar [https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git)
git fetch exemplar
base=$(git merge-base main exemplar/main)
git branch pre-copier-backup main
git switch --detach "$base"

tmpdir=$(mktemp -d)
python3 -m venv "$tmpdir/venv"
"$tmpdir/venv/bin/python3" -m pip install copier pre-commit
"$tmpdir/venv/bin/copier" copy --trust --overwrite \
-d project_name=your_project_name \
-d maintainer=your_github_username \
-d minimum_cpp_build_version=20 \
-d paper=PnnnnRr \
-d description="Short project description." \
-d unit_test_library=gtest \
-d generating_exemplar=false \
-d owner=bemanproject \
[https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git) \
.
"$tmpdir/venv/bin/pre-commit" run --all-files || true
git add .
git switch -c stamp
git commit -m "Bootstrap Copier template"

git switch main
git rebase --rebase-merges --onto stamp "$base" main
```

After the rebase, commit the generated `.copier-answers.yml`. That file is what enables future template updates with:

```shell
uvx --from copier copier update --trust
```

If you want to track a fork of exemplar rather than `bemanproject/exemplar`, edit `_src_path` in `.copier-answers.yml` before your first `copier update`.

## Template Maintenance

If you are changing the template itself rather than developing a library, use the Copier workflow directly:

* Edit `copier.yml` for template questions, defaults, validators, and post-copy tasks.
* Edit `template/` for files that should be rendered into stamped projects.
* Run `./copier/check_copier.sh` to verify exemplar self-regeneration and non-exemplar templating.
* Create a new project locally using the Quick Start commands when you want to use the template for real work.

The consistency check renders from a `.git`-free temporary snapshot of the repository. That keeps local validation aligned with the current worktree contents, rather than only the last committed Git state.

---

What follows is an example of a Beman library README.

# beman.exemplar: A Beman Library Exemplar

<!-- markdownlint-disable-next-line line-length -->
![Library Status](https://raw.githubusercontent.com/bemanproject/beman/refs/heads/main/images/badges/beman_badge-beman_library_under_development.svg) ![Continuous Integration Tests](https://github.com/bemanproject/exemplar/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/bemanproject/exemplar/actions/workflows/pre-commit-check.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/bemanproject/exemplar/badge.svg?branch=main)](https://coveralls.io/github/bemanproject/exemplar?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp29.svg) [![Compiler Explorer Example](https://img.shields.io/badge/Try%20it%20on%20Compiler%20Explorer-grey?logo=compilerexplorer&logoColor=67c52a)](https://godbolt.org/z/4qEPK87va)

`beman.exemplar` is a minimal C++ library conforming to [The Beman Standard](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md).
This can be used as a template for those intending to write Beman libraries.
It may also find use as a minimal and modern C++ project structure.
It may also find use as a minimal and modern C++ project structure.

**Implements**: `std::identity` proposed in [Standard Library Concepts (P0898R3)](https://wg21.link/P0898R3).

Expand Down
Loading