Skip to content

Add Copier migration support and update template docs - #393

Open
steve-downey wants to merge 24 commits into
bemanproject:mainfrom
steve-downey:copier
Open

steve-downey wants to merge 24 commits into
bemanproject:mainfrom
steve-downey:copier

Conversation

@steve-downey

Copy link
Copy Markdown
Member

Summary

This switches exemplar's template workflow over to Copier in a way that is usable for both new stamped repositories and older forks that started life as a plain GitHub template copy.

Copier is a modern project templating tool for keeping a generated repository tied back to its source template. In practice, that gives us two things we want here:

  • a declarative template definition in copier.yml
  • a persistent answers file that lets generated repositories run copier update later

Why

The previous flow was effectively a one-shot stamp-out process. It could generate a project, but it did not leave enough metadata behind for template-driven updates afterward.

This change closes that gap by making stamped projects retain the information Copier needs to understand:

  • which template they came from
  • which template revision they were generated from
  • which answers were used during generation

That lets older exemplar-based repositories be rebased onto a Copier-generated baseline and then participate in template updates going forward.

What changed

  • add a rendered .copier-answers.yml template so generated repositories keep Copier metadata
  • seed stable template source and commit information during stamp.sh and local template validation
  • update the self-check to account for the answers file while preserving the existing round-trip checks
  • document how to migrate an already-copied exemplar repository onto a Copier-managed base
  • sync the generated exemplar docs with the template sources

Parity

At this point the Copier-based workflow is functionally at parity with the prior cookiecutter-style stamping flow:

  • new projects can still be stamped from exemplar with the same practical outcome
  • local template validation still round-trips exemplar and checks for leakage into non-exemplar output
  • maintainers now also get persisted template metadata, which unlocks future copier update support

So this is not a reduction in template capability; it preserves the existing stamp-out behavior and adds an update path that the earlier flow did not provide.

Validation

  • ./copier/check_copier.sh

@steve-downey

Copy link
Copy Markdown
Member Author

A few reviewer notes to make the diff easier to scan:

  • The functional change is centered on template/.copier-answers.yml.jinja plus the hidden template_src_path and template_commit questions in copier.yml. Those are what make generated repositories updateable by Copier later.
  • stamp.sh and copier/check_copier.sh now explicitly seed canonical template metadata because both render from a .git-free snapshot. Without that, stamped repos would not get a usable _src_path / _commit pair.
  • The README/CONTRIBUTING changes are mostly there to document the migration path for older exemplar clones and to keep the generated exemplar docs in sync with the template sources.
  • The parity claim is intentionally narrow: stamp-out behavior and local validation still work as before, while Copier now also persists enough metadata for a future copier update flow.

Validation run locally:

  • ./copier/check_copier.sh

@ednolan

ednolan commented May 8, 2026

Copy link
Copy Markdown
Member

I need to review this more thoroughly but on a first skim-through this looks good

@coveralls

coveralls commented May 17, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 100.0%. remained the same — steve-downey:copier into bemanproject:main

@steve-downey
steve-downey marked this pull request as draft May 22, 2026 15:48
@steve-downey

Copy link
Copy Markdown
Member Author

Moved to Draft: Not high risk that anyone would merge, but there are a few round-trip issues I'm cleaning up, as well as docs and scripts on how to use the copier infrastructure.
In particular, keeping the connection and history from exemplar as stamp.sh does shouldn't be necessary as copier has its own tools for working with git to update with new features from the template.

@ednolan

ednolan commented May 22, 2026

Copy link
Copy Markdown
Member

I do want to move from cookiecutter to copier, but I would really prefer to keep around the .exemplar_version file/mechanism, since I need it for some of my own internal tooling that keeps repositories up to date, which copier doesn't entirely replace for me.

@steve-downey

Copy link
Copy Markdown
Member Author

Reviewer Guide: Copier Automation and Testing

These latest changes finalize the template maintenance pipeline to ensure the template never drifts from the reference implementation and doesn't break downstream users.

What to look at:

update_templates.py: A new automation script that synchronizes root codebase changes directly into the template Jinja files.
test_standard_project.sh & ci_tests.yml: I've added active testing for the template output in CI. It generates, builds, and tests downstream standalone projects across a matrix of configurations (Catch2/GTest and Modules ON/OFF).
MAINTAINERS.md: New documentation explaining the update loop and troubleshooting steps.
What to look for (and why this branch is ahead of main):

You will notice an infra submodule bump and a CODEOWNERS tweak in this PR that aren't on main yet. I deliberately included these as the payload to test the Copier update machinery.
Validation:

To prove the update path, I executed a live copier update against the downstream transcode project. It safely delivered the infra changes without touching transcode's custom Catch2 logic. The project built flawlessly, passed its test suite, and I have successfully pushed the update.

@steve-downey
steve-downey marked this pull request as ready for review May 23, 2026 21:53
@steve-downey

steve-downey commented May 23, 2026 •

Copy link
Copy Markdown
Member Author

Pull Request Summary

This PR migrates the Exemplar templating from Cookiecutter to Copier. Over the course of stabilizing this migration, several underlying infrastructure and CI testing issues became hard blockers for the matrix pipeline. As a result, this PR includes some vital infrastructure fixes that are strictly outside the scope of Copier, but were integral to getting the 120+ CI checks consistently completely green.

Here is the breakdown of the changes:

1. Primary Copier Migration (Core PR Scope)

  • Template Engine Transition: Migrated from Python Cookiecutter to Copier, adopting copier.yml for explicit configuration, lifecycle tracking, and template updates.
  • Sync Synchronization Scripts: Added update_templates.py to seamlessly sync changes from the repo root directly into the .jinja templates natively, alongside check_copier.sh to enforce parity.
  • Streamlined Validation Tooling: Updated test_standard_project.sh to leverage uvx copier to generate and test variants dynamically (GTest, Catch2, Modules).

2. Infrastructure & CI Matrix Fixes (Integral Fixes)

To achieve a passing pipeline, the following underlying testing infrastructure bugs were patched out (which otherwise would have been separate PRs):

  • Robust CMake Version Validation Matrix: Added a new explicit CI job (copier-cmake-matrix) and test_cmake_matrix.sh wrapper. This fetches pristine CMake binaries across a matrix of versions (3.30 to 4.3.2) dynamically via uv virtual environments, guaranteeing that templates natively compile correctly across all consumer CMake patch boundaries.
  • MSVC Module & CMake 4.3.3 Support: The CI runners pulling latest CMake binaries functionally broke CXX_MODULE_STD toolchain detection for CMake 4.3.3. I patched infra/cmake/enable-experimental-import-std.cmake to specifically map the correct experimental UUIDs for 4.3.3.
  • Submodule Tracking Mitigation: Because the aforementioned CMake toolchain patch is still pending upstream in bemanproject/infra#62, infra/.beman_submodule in this PR has been temporarily pointed to a local fork commit to bypass the strict beman-submodule check sync drift during workflows.
  • Jinja Action Formatting & Linting Parity: Resolved latent end-of-file-fixer hook failures in .github/workflows/vcpkg-release.yml caused by Jinja whitespace eaters ({%- endraw %}) dropping necessary EOF newlines, along with escaping nested ${{ matrix }} evaluation hooks in workflows so copier doesn't mangle GitHub Action executions.

Merge Note: Once this merges and upstream bemanproject/infra#62 resolves, we can point the infra submodule configuration completely back to bemanproject/infra on main.

@ClausKlein ClausKlein left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really necessary that a copy of infra submodule is under templates?
It may be created with Beman-submodule, or not?

@steve-downey

Copy link
Copy Markdown
Member Author

I do want to move from cookiecutter to copier, but I would really prefer to keep around the .exemplar_version file/mechanism, since I need it for some of my own internal tooling that keeps repositories up to date, which copier doesn't entirely replace for me.

That's just capturing the commit sha into .exemplar_version using

Path(".exemplar_version").write_text(sha + "\n")
, yes?
There's a post install hook for copier, too, so I think I can just move it over.

@steve-downey

Copy link
Copy Markdown
Member Author

Is it really necessary that a copy of infra submodule is under templates? It may be created with Beman-submodule, or not?

I can change that.
I prefer to track infra locally because when I make changes to infra they need to be tracked as part of the project. But the way to do that is better support for subtree when using beman submodules, not making that decision for you.

@ednolan

ednolan commented Jun 14, 2026

Copy link
Copy Markdown
Member

Sounds good

@steve-downey

Copy link
Copy Markdown
Member Author

Cookiecutter vs Copier cross-comparison as of ba2f9ed

Generated a non-exemplar project with identical parameters using cookiecutter from main and copier from this branch. check_copier.sh passes for both the exemplar self-test and the non-exemplar leak check.

Every remaining difference is either expected (Copier metadata) or an improvement. Full diff below.

Summary

Difference Category
.copier-answers.yml only in copier Expected — Copier metadata for future copier update
.gitattributes: no cookiecutter/** line Improvement — non-exemplar projects don't have cookiecutter/
ci_tests.yml / pre-commit-update.yml cron values Expected — different random seeds per engine
infra/.beman_submodule commit hash Improvement — copier ships latest upstream (7b66b85)
infra/cmake/enable-experimental-import-std.cmake Improvement — refactored from 194-line per-version table to 24-line range blocks
port/portfile.cmake.in formatting Improvement — gersemi-formatted file(REMOVE_RECURSE
.pre-commit-config.yaml exclude pattern Improvement — no stale cookiecutter/ reference
README.md one blank line after badges Minor cosmetic — functionally identical markdown rendering

Exact diff (excluding enable-experimental-import-std.cmake bulk refactor)

Only in copier: .copier-answers.yml

diff -r -u cookiecutter/.gitattributes copier/.gitattributes
--- cookiecutter/.gitattributes
+++ copier/.gitattributes
@@ -1,5 +1,4 @@
 infra/** linguist-vendored
-cookiecutter/** linguist-vendored
 *.bib -linguist-detectable
 *.tex -linguist-detectable
 papers/* linguist-documentation

diff -r -u cookiecutter/.github/workflows/ci_tests.yml copier/.github/workflows/ci_tests.yml
--- cookiecutter/.github/workflows/ci_tests.yml
+++ copier/.github/workflows/ci_tests.yml
@@ -9,7 +9,7 @@
   pull_request:
   workflow_dispatch:
   schedule:
-    - cron: '14 17 * * 6'
+    - cron: '30 15 * * 6'

diff -r -u cookiecutter/.github/workflows/pre-commit-update.yml copier/.github/workflows/pre-commit-update.yml
--- cookiecutter/.github/workflows/pre-commit-update.yml
+++ copier/.github/workflows/pre-commit-update.yml
@@ -5,7 +5,7 @@
   schedule:
-    - cron: "8 13 * * 4"
+    - cron: "0 16 * * 0"

diff -r -u cookiecutter/infra/.beman_submodule copier/infra/.beman_submodule
--- cookiecutter/infra/.beman_submodule
+++ copier/infra/.beman_submodule
@@ -1,3 +1,3 @@
 [beman_submodule]
 remote=https://github.com/bemanproject/infra.git
-commit_hash=d536fc285ae058cf8f5b736b5ff73d18a421b296
+commit_hash=7b66b858d7f48428fca936184aef2bd246ccc81a

diff -r -u cookiecutter/infra/cmake/enable-experimental-import-std.cmake copier/infra/cmake/enable-experimental-import-std.cmake
  (194-line per-version elseif table → 24-line VERSION_GREATER_EQUAL range blocks)

diff -r -u cookiecutter/port/portfile.cmake.in copier/port/portfile.cmake.in
--- cookiecutter/port/portfile.cmake.in
+++ copier/port/portfile.cmake.in
@@ -29,7 +29,8 @@
 if(NOT "modules" IN_LIST FEATURES)
-    file(REMOVE_RECURSE
+    file(
+        REMOVE_RECURSE

diff -r -u cookiecutter/.pre-commit-config.yaml copier/.pre-commit-config.yaml
--- cookiecutter/.pre-commit-config.yaml
+++ copier/.pre-commit-config.yaml
@@ -46,4 +46,4 @@
-exclude: 'cookiecutter/|infra/|port/'
+exclude: 'infra/|port/'

diff -r -u cookiecutter/README.md copier/README.md
--- cookiecutter/README.md
+++ copier/README.md
@@ -10,7 +10,6 @@
 ![Standard Target]...
-
 <!-- markdownlint-restore -->

There may be further changes to fine-tune non-exemplar output.

Replace the Cookiecutter-based template under cookiecutter/ with a
Copier-based template under template/. The new layout uses copier.yml
for declarative configuration and .jinja suffixed files for template
rendering.

Key changes:
- Delete cookiecutter/ directory and its hooks/config
- Add copier.yml with project questions, defaults, and post-copy tasks
- Create template/ with Jinja2 versions of all project files
- Add copier/check_copier.sh for round-trip validation
- Add template/.copier-answers.yml.jinja so generated projects retain
  Copier metadata for future template updates
- Update stamp.sh to use Copier instead of Cookiecutter
- Document migration path in README and CONTRIBUTING

The .copier-answers.yml persisted in generated projects enables
copier update for template-driven maintenance going forward.
Rewrite the README to describe the Copier-based project lifecycle:
quick-start generation with uvx, incubation workflow, transfer to
the bemanproject org, and ongoing template updates.

Add instructions for rebasing older exemplar clones onto a
Copier-managed baseline and document the reconfiguration workflow
using copier update --data.

Update CONTRIBUTING.md with Copier template development guidelines
and sync template/README.md.jinja with the new content.
Move cron schedule randomization into copier.yml questions with
computed defaults so each generated project gets unique schedules.
Remove the per-workflow randomization from the Jinja templates.
Add automation and CI infrastructure for maintaining and validating
the Copier template:

- copier/update_templates.py: synchronizes root codebase changes
  into the template/ Jinja files
- copier/test_standard_project.sh: generates and tests template
  output across variants (GTest/Catch2, Modules ON/OFF)
- copier/test_cmake_matrix.sh: validates template output against
  CMake versions 3.30 through 4.3.x

Add two new CI jobs to ci_tests.yml:
- copier-test: runs test_standard_project.sh on gcc-release
- copier-cmake-matrix: runs test_cmake_matrix.sh across a version
  matrix, both guarded by generating_exemplar so they only run in
  exemplar itself

Also fix EOF whitespace in vcpkg-release.yml.jinja and update
portfile.cmake.in template for Copier variable syntax.
Document the template update loop, troubleshooting steps, and
Copier maintenance workflow for project maintainers.
Update infra to bemanproject/infra@7b66b85 which simplifies the
experimental import-std UUID rules into concise version-range
blocks covering CMake 3.30 through 4.3.x.

Sync the template copy of enable-experimental-import-std.cmake
and .beman_submodule with the new upstream state.
Fix discrepancies that check_copier.sh catches between the Copier
template output and the actual exemplar repo:

- Bump template version from 2.4.0 to 2.4.1 in CMakeLists.txt.jinja
- Update infra-workflows refs from @1.7.2 to @1.7.3 in ci_tests and
  pre-commit-update templates
- Fix {%- endraw %} to {% endraw %} in pre-commit-update and
  vcpkg-release templates to preserve trailing newlines
- Add blank line between cron schedule and concurrency in ci_tests
- Update README badge format to multi-line clickable badges matching
  main's style
- Exclude .claude/ from check_copier.sh diff

check_copier.sh now passes cleanly for both the exemplar self-test
and the non-exemplar project generation.
Restructure template/README.md.jinja so the heading, badges,
description, license, and usage sections are shared between
exemplar and non-exemplar output, matching what cookiecutter
produced. Previously the non-exemplar branch rendered only "TODO"
with no heading or badges.

Guard exemplar-specific paths in .gitattributes.jinja (template/,
copier/) and .pre-commit-config.yaml.jinja exclude pattern so
generated non-exemplar projects don't reference directories they
don't contain.

Fix extra blank line in CONTRIBUTING.md.jinja rendering and remove
the include-path NOTE that main deliberately removed.
beman-tidy requires SPDX-License-Identifier in all files within
the first 25 lines. Add the header to the answers file template
so generated projects pass beman-tidy out of the box.
- test_standard_project.sh: drop two stray lines above the shebang that
  demoted it off line 1, causing the script to run under /bin/sh and fail
  on ${BASH_SOURCE[0]} ("Bad substitution").
- Add copier/format_project.sh, run as a post-generation copier task, to
  reformat the generated tree with the project's own formatters (gersemi,
  clang-format). gersemi/clang-format wrapping depends on the resolved
  project-name length, so the static template cannot be pre-formatted for
  every name; without this a fresh project fails its own pre-commit/CI on
  the first run. No-op for the exemplar, so copier/exemplar consistency
  (check_copier.sh) is preserved.
- Add copier/lint_standard_project.sh and a copier-lint CI job that
  generates a standard project and asserts it self-lints cleanly.
The exemplar's ci_tests.yml carried a copier-lint job that the template
did not generate, causing check_copier.sh consistency check to fail. Add
the job to the template inside the generating_exemplar block so stamped
output matches the source.
@ClausKlein

Copy link
Copy Markdown
Contributor

Please update the infra subtree, CMake v4.4 is released.

Comment thread .github/workflows/ci_tests.yml Outdated
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"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Why are v4.4, v4.3.3, v4.2.7, v4.1.6, v4.0.7, ... missing?
  • What is the intention of this test matrix?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

matches the selection of GUIDs in infra/cmake/enable-experimental-import-std.cmake

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand right this version set?

I would check the latest released versions: 3.30.x, 3.31.x, 4.0.x, 4.1.x, 4.2.x, 4.3.x, and 4.4.x

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But those are not when the GUID that is changing, changed.
There's no way to reason about them, you have to look at the CMake code and release notes to see what versions bumped the key. We did that exercise for this key.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO: we should test all latest patched cmake versions to see, if a bugfixd cmake release version has changed the UUID:

- uses: lukka/get-cmake@latest
    with:
      cmakeVersion: "~3.30.0"  # <--= optional, use most recent 3.30.x version

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But: as long modules are NOT tested, we can omit this test totally!

bash-5.3$ tail -13 copier/test_standard_project.sh 

# 1. GTest + No Modules
test_project_variant "gtest-no-modules" "gtest" "false"

# 2. Catch2 + No Modules
test_project_variant "catch2-no-modules" "catch2" "false"

# Do not run modules locally if we cannot guarantee modern tooling, but CI will use clang/gcc containers
# We check if we are in github actions to enforce building modules, as locally it may fail CMake module requirements.

echo "=========================================================="
echo "✔ All variants successfully generated, built, and tested! "
echo "=========================================================="
bash-5.3$ 

@ClausKlein

Copy link
Copy Markdown
Contributor

I tried to play with your branch, but:

bash-5.3$ uvx --from copier copier copy . ../my-new-library

Copying from template version 2.4.1
    create  infra
    create  infra/LICENSE
    create  infra/cmake
    create  infra/cmake/msvc-toolchain.cmake
    create  infra/cmake/llvm-libc++-toolchain.cmake
    create  infra/cmake/appleclang-toolchain.cmake
    create  infra/cmake/gnu-toolchain.cmake
    create  infra/cmake/beman-install-library.cmake
    create  infra/cmake/Config.cmake.in
    create  infra/cmake/enable-experimental-import-std.cmake
    create  infra/cmake/llvm-toolchain.cmake
    create  infra/cmake/use-fetch-content.cmake
    create  infra/cmake/BuildTelemetry.cmake
    create  infra/cmake/BuildTelemetryConfig.cmake
    create  infra/cmake/telemetry.sh
    create  infra/.pre-commit-config.yaml
    create  infra/README.md
    create  infra/.gitignore
    create  infra/.beman_submodule
    create  infra/.github
    create  infra/.github/CODEOWNERS
    create  infra/.github/workflows
    create  infra/.github/workflows/pre-commit.yml
    create  infra/.github/workflows/reusable-beman-create-issue-when-fault.yml
    create  CMakeLists.txt
    create  LICENSE
    create  stamp.sh
    create  .beman-tidy.yaml
    create  images
    create  images/use-this-template.png
    create  .pre-commit-config.yaml
    create  include
    create  include/beman
    create  include/beman/exemplar
    create  include/beman/exemplar/exemplar.hpp
    create  include/beman/exemplar/CMakeLists.txt
    create  include/beman/exemplar/config.hpp
    create  include/beman/exemplar/identity.hpp
    create  include/beman/exemplar/exemplar.cppm
    create  include/beman/exemplar/config_generated.hpp.in
    create  tests
    create  tests/beman
    create  tests/beman/exemplar
    create  tests/beman/exemplar/CMakeLists.txt
    create  tests/beman/exemplar/identity.test.cpp
    create  cookiecutter
Error rendering template path cookiecutter/{{cookiecutter.project_name}}: 'cookiecutter' is undefined
bash-5.3$

@ClausKlein

Copy link
Copy Markdown
Contributor

I have extracted a zip archive from my branch:

uvx --from copier copier copy . ../my-new-library
Template uses potentially unsafe feature: tasks.
If you trust this template, consider adding the `--trust` option when running `copier copy/update`.

bash-5.3$ tail -15 copier.yml

_tasks:
  - >-
    if [ "{{ generating_exemplar }}" != "True" ] && [ "{{ generating_exemplar }}" != "true" ]; then
      mv "include/beman/{{ project_name }}/identity.hpp" "include/beman/{{ project_name }}/todo.hpp";
      mv "examples/identity_direct_usage.cpp" "examples/todo.cpp";
      rm "examples/identity_as_default_projection.cpp";
      mv "tests/beman/{{ project_name }}/identity.test.cpp" "tests/beman/{{ project_name }}/todo.test.cpp";
      git ls-remote https://github.com/bemanproject/exemplar.git HEAD | awk '{print $1}' > .exemplar_version;
    fi
  # Reformat the generated tree with the project's own formatters (gersemi,
  # clang-format) so it is lint-clean on the first pre-commit / CI run. This
  # is a no-op for the already-formatted exemplar, preserving copier/exemplar
  # consistency (see copier/check_copier.sh).
  - bash "{{ _copier_conf.src_path }}/copier/format_project.sh"

@ClausKlein

ClausKlein commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

I finished my test, it works. LGTM!

Only the directory name and the project name must be the same!
if not, beman-tidy fails.

How should we format python scripts?

black --check copier/*.py
would reformat copier/update_templates.py

Oh no! 💥 💔 💥
1 file would be reformatted.
bash-5.3$ pylint copier/*.py
************* Module update_templates
copier/update_templates.py:10:0: R0914: Too many local variables (22/15) (too-many-locals)
copier/update_templates.py:29:8: W1510: 'subprocess.run' used without explicitly defining the value for 'check'. (subprocess-run-check)
copier/update_templates.py:53:17: W1510: 'subprocess.run' used without explicitly defining the value for 'check'. (subprocess-run-check)
copier/update_templates.py:82:17: W1514: Using open without explicitly specifying an encoding (unspecified-encoding)
copier/update_templates.py:91:25: W1514: Using open without explicitly specifying an encoding (unspecified-encoding)
copier/update_templates.py:97:25: W1514: Using open without explicitly specifying an encoding (unspecified-encoding)
copier/update_templates.py:8:0: W0611: Unused import stat (unused-import)

-----------------------------------
Your code has been rated at 8.79/10

bash-5.3$ 

@steve-downey

Copy link
Copy Markdown
Member Author

I finished my test, it works. LGTM!

Only the directory name and the project name must be the same!
if not, beman-tidy fails.

I think we need to figure out how to relax that at least for local checks. The name of '.' on your hard drive ought to be up to you. Even the name of my fork can be over-constrained.
I think I created an issue already.

Re: formatting, python
I don't care much, so I usually just use black so as not to have to think or argue. Or whatever my python infrastructure colleagues are setting as the default this quarter.

I have noticed, though, that because generated names get embedded in the code, the generated files may not initially pass format lint. I think we can add a post format pass, although that means saying you trust the template to run scripts.

Exemplar self-test works partly because exemplar is a short name and a different short name doesn't change enough line lengths.

@ClausKlein

ClausKlein commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

@steve-downey I do not understand what goes wrong here:

bash-5.3$ uvx --from copier copier copy "git+[https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git)" ../my-new-library
 # ...
plumbum.commands.processes.ProcessExecutionError: Unexpected exit code: 128
Command line: | /usr/local/bin/git ls-remote --tags --refs '[https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git)'
Stderr:       | fatal: protocol '[https' is not supported
bash-5.3$ git remote -v
claus	git@github.com:ClausKlein/exemplar.git (fetch)
claus	git@github.com:ClausKlein/exemplar.git (push)
origin	https://github.com/bemanproject/exemplar.git (fetch)
origin	https://github.com/bemanproject/exemplar.git (push)
steve-downey	https://github.com/steve-downey/exemplar.git (fetch)
steve-downey	https://github.com/steve-downey/exemplar.git (push)
bash-5.3$ git status
On branch feature/copier
Your branch is up to date with 'claus/feature/copier'.

nothing to commit, working tree clean
bash-5.3$

but this works, only the .exemplar_version is wrong in this case:

bash-5.3$ uvx --from copier copier copy --trust --vcs-ref HEAD . ../my-new-library
🎤 Name of the generated repository and library.
   my_project_name
🎤 GitHub username of the project maintainer.
   your_github_username
🎤 Minimum C++ language version to require when building.
   20
🎤 WG21 paper number associated with the library.
   PnnnnRr
🎤 Short project description.
   Short project description.
🎤 Unit test library to configure.
   gtest

Copying from template version 2.4.1.post23.dev0+6f2cee4
# ...

bash-5.3$ git describe --tags --dirty
v2.4.1-23-g6f2cee4

bash-5.3$ cat ../my-new-library/.
./                       .beman-tidy.yaml         .copier-answers.yml      .gitattributes           .gitignore               .pre-commit-config.yaml  
../                      .clang-format            .exemplar_version        .github/                 .markdownlint.yaml       
bash-5.3$ cat ../my-new-library/.exemplar_version 
d5f59d1b070c370b85b02630f855429c2da4ddc3        <<<<<<<<< this is wrong!
bash-5.3$ 

@ClausKlein

ClausKlein commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor

@steve-downey @ednolan please note:

I continued with my usage tests

  • The _task: section should not used and may results in wrong .exampler_version contents!
  • the formatting runs multiple times and may use other tool versions!
  • To prevent problems with cookiecutter in git history, we need to use the -vcs-ref HEAD option too!
bash-5.3$ copier update --data minimum_cpp_build_version=26 --vcs-ref HEAD 
Template uses potentially unsafe feature: tasks.
If you trust this template, consider adding the `--trust` option when running `copier copy/update`.
bash-5.3$ copier update --data minimum_cpp_build_version=26 --trust --vcs-ref HEAD 
Keeping template version 2.4.1.post31.dev0+a07b85d
Formatting 4 CMake file(s) with gersemi 0.28.0...
Warning: unknown command 'beman_install_library' used at:
/private/var/folders/wb/ckvxxgls5db7qyhqq4y5_l1c0000gq/T/copier._main.old_copy.nq92ecxg/CMakeLists.txt:80:1

Warning: unknown command 'configure_build_telemetry' used at:
/private/var/folders/wb/ckvxxgls5db7qyhqq4y5_l1c0000gq/T/copier._main.old_copy.nq92ecxg/CMakeLists.txt:81:1

Formatting 6 C/C++ file(s) with clang-format 22.1.8...
🎤 Name of the generated repository and library.
   my_project_name
🎤 GitHub username of the project maintainer.
   ClausKlein
🎤 WG21 paper number associated with the library.
   PnnnnRr
🎤 Short project description.
   Short project description.
🎤 Unit test library to configure.
   gtest
Formatting 4 CMake file(s) with gersemi 0.28.0...
Warning: unknown command 'beman_install_library' used at:
/Users/clausklein/Workspace/cpp/beman-project/my_project_name/CMakeLists.txt:80:1

Warning: unknown command 'configure_build_telemetry' used at:
/Users/clausklein/Workspace/cpp/beman-project/my_project_name/CMakeLists.txt:81:1

Formatting 6 C/C++ file(s) with clang-format 22.1.8...
Formatting 4 CMake file(s) with gersemi 0.28.0...
Warning: unknown command 'beman_install_library' used at:
/private/var/folders/wb/ckvxxgls5db7qyhqq4y5_l1c0000gq/T/copier._main.new_copy.ammfp15p/CMakeLists.txt:80:1

Warning: unknown command 'configure_build_telemetry' used at:
/private/var/folders/wb/ckvxxgls5db7qyhqq4y5_l1c0000gq/T/copier._main.new_copy.ammfp15p/CMakeLists.txt:81:1

Formatting 6 C/C++ file(s) with clang-format 22.1.8...
Make sure Git >= 2.24 is installed to improve updates.

bash-5.3$ cat .exemplar_version 
v2.4.1-31-ga07b85d
bash-5.3$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   .copier-answers.yml
	modified:   CMakePresets.json
	modified:   CONTRIBUTING.md
	modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
bash-5.3$ git diff
diff --git a/.copier-answers.yml b/.copier-answers.yml
index 8028152..7bee833 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -4,7 +4,7 @@ _commit: v2.4.1-31-ga07b85d
 _src_path: gh:ClausKlein/exemplar
 description: Short project description.
 maintainer: ClausKlein
-minimum_cpp_build_version: '20'
+minimum_cpp_build_version: '26'
 paper: PnnnnRr
 project_name: my_project_name
 unit_test_library: gtest
# ....

The .copier-answers.yml contains all needed information for later updates:
bash-5.3$ head .copier-answers.yml

# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# Standard answers and internal state managed by Copier
_commit: v2.4.1-31-ga07b85d
_src_path: gh:ClausKlein/exemplar
description: Short project description.
maintainer: ClausKlein
minimum_cpp_build_version: '26'
paper: PnnnnRr
project_name: my_project_name
unit_test_library: gtest

@ClausKlein

Copy link
Copy Markdown
Contributor

I tried to play with your branch, but:

bash-5.3$ uvx --from copier copier copy . ../my-new-library

Copying from template version 2.4.1
    create  infra
...
    create  cookiecutter
Error rendering template path cookiecutter/{{cookiecutter.project_name}}: 'cookiecutter' is undefined
bash-5.3$

from Templates versions

By default, Copier will copy from the last release found in template Git tags!

sorted as PEP 440, regardless of whether the template is from a URL or a local clone of a Git repository.

steve-downey and others added 5 commits July 31, 2026 22:31
# Conflicts:
#	cookiecutter/check_cookiecutter.sh
#	cookiecutter/{{cookiecutter.project_name}}/CMakeLists.txt
Add a README section explaining that copier copy/update default to the
latest release tag rather than the tip of the default branch, and how to
override with --vcs-ref (HEAD, a branch, or a tag/commit).

Rework the migration section into a drift-based decision: rebase recent
exemplar/stamp.sh clones onto a Copier baseline, or start fresh and port
for old cookiecutter-era projects that share no usable ancestor.

Add maintainer notes on working from a fork (_src_path tracking, tagging
semantics, the dual-role invariant) and on why rebasing published
template history breaks downstream copier update via orphaned _commit
hashes.
Two changes landed in the generated exemplar files without the
corresponding edit under template/, so re-stamping no longer reproduced
the repository and ./copier/check_copier.sh failed:

* Bump cmake_minimum_required to 3.30...4.4 in the CMakeLists template,
  matching the root CMakeLists.txt.
* Add the "Choosing a Template Version" section and the reworked
  "Migrating an Existing Project onto Copier" section (drift triage plus
  Case 1 and Case 2) to the README template.
Rendering the template with a non-exemplar project name leaks the
exemplar's own CMake option names into the generated project:

* README.md told users to set BEMAN_EXEMPLAR_BUILD_EXAMPLES rather than
  BEMAN_<PROJECT>_BUILD_EXAMPLES. The cookiecutter template on main
  parameterized this correctly; it was lost in the Copier migration.
* tests/beman/<name>/CMakeLists.txt guarded CXX_MODULE_STD on
  BEMAN_EXEMPLAR_USE_MODULES, so a generated project's tests never
  picked up module std when its own USE_MODULES option was enabled.
  This one is inherited from the cookiecutter template.

check_copier.sh did not catch either: its leak grep is case sensitive
and these names are uppercase. Both render identically for the exemplar
itself, so the generated exemplar tree is unchanged.

The remaining BEMAN_EXEMPLAR uses in identity.hpp and identity.test.cpp
sit inside generating_exemplar guards and are correct.
CMakeLists.txt now declares a policy range of 3.30...4.4, and
infra/cmake/enable-experimental-import-std.cmake grew a branch for
CMake >= 4.4.0 using the f35a9ac6 import-std UUID, but the matrix
stopped at 4.3.2, so that branch went untested.

Confirmed 4.4.2 selects the intended UUID: the CMake binaries accept
f35a9ac6 in both 4.4.0 and 4.4.2 and not in 4.3.x, so the whole
published 4.4 series (PyPI ships 4.4.0 and 4.4.2 only) maps to the
same infra branch. ./copier/test_cmake_matrix.sh gcc-release 4.4.2
generates, configures, builds, and tests both project variants.
@steve-downey

Copy link
Copy Markdown
Member Author

Back to green, no semantic gaps with main (to the best of my knowledge).
I'd like to land this before we hit another regression?
@ednolan @ClausKlein

@ClausKlein

Copy link
Copy Markdown
Contributor

Back to green, no semantic gaps with main (to the best of my knowledge). I'd like to land this before we hit another regression? @ednolan @ClausKlein

LGTM, I am locking forward to use ist soon.

Comment thread copier.yml Outdated
mv "examples/identity_direct_usage.cpp" "examples/todo.cpp";
rm "examples/identity_as_default_projection.cpp";
mv "tests/beman/{{ project_name }}/identity.test.cpp" "tests/beman/{{ project_name }}/todo.test.cpp";
git ls-remote https://github.com/bemanproject/exemplar.git HEAD | awk '{print $1}' > .exemplar_version;

@ClausKlein ClausKlein Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only this may be still a wrong git tag depending on the used copier argument!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah!

git ls-remote https://github.com/bemanproject/exemplar.git HEAD | awk '{print $1}' > .exemplar_version;

Need to double check against what the matching version of the template is.
The snag is that it really ought to have a URL, but that's probably a rare enough use case to not worry about.
It would need to be something like the git merge-base for the branch, unless it's the commit itself that is going to land on upstream bemanproject/exemplar soon.
That's probably enough reason to just record the commit of the copier template.

steve-downey and others added 3 commits August 10, 2026 14:08
Capture the git SHA of the exemplar repository used to generate the project.

After running the reformat pass, create and commit the project.
test_standard_project.sh only ever built the no-modules variants, so
copier-cmake-matrix never reached
infra/cmake/enable-experimental-import-std.cmake and never validated the
per-release CMAKE_EXPERIMENTAL_CXX_IMPORT_STD values the matrix exists to
check. Its comment claimed CI enforced a modules build, but no such check
was there.

Add a gtest + modules variant, run under GITHUB_ACTIONS with CMake 3.31
or later. CMake 3.30 cannot discover `import std` support for GCC at all,
independent of the UUID, so it stays on the no-modules variants.
@steve-downey

Copy link
Copy Markdown
Member Author

We're back to green, and there is now test coverage for the cmake module support (white box -- UUID changes).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants