Skip to content

Add Copier migration support and update template docs#393

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

Add Copier migration support and update template docs#393
steve-downey wants to merge 9 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.

Comment thread .github/workflows/vcpkg-release.yml Outdated
@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.
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