Repository navigation
Add Copier migration support and update template docs - #393
steve-downey wants to merge 24 commits into
Conversation
|
A few reviewer notes to make the diff easier to scan:
Validation run locally:
|
|
I need to review this more thoroughly but on a first skim-through this looks good |
|
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 |
|
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. |
|
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. 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. 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. |
Pull Request SummaryThis 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)
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):
Merge Note: Once this merges and upstream |
ClausKlein
left a comment
There was a problem hiding this comment.
Is it really necessary that a copy of infra submodule is under templates?
It may be created with Beman-submodule, or not?
That's just capturing the commit sha into .exemplar_version using , yes?There's a post install hook for copier, too, so I think I can just move it over. |
I can change that. |
|
Sounds good |
Cookiecutter vs Copier cross-comparison as of ba2f9edGenerated a non-exemplar project with identical parameters using cookiecutter from Every remaining difference is either expected (Copier metadata) or an improvement. Full diff below. Summary
Exact diff (excluding
|
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.
|
Please update the infra subtree, CMake v4.4 is released. |
| 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"] |
There was a problem hiding this comment.
- 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?
There was a problem hiding this comment.
matches the selection of GUIDs in infra/cmake/enable-experimental-import-std.cmake
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 versionThere was a problem hiding this comment.
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$ |
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$ |
|
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" |
I finished my test, it works. LGTM!Only the 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$ |
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. Re: formatting, python 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. |
|
@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$ |
Fix beman-tidy driven changes in the copier branch.
Copier readme
|
@steve-downey @ednolan please note: I continued with my usage tests
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 # 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 |
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. |
# 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.
|
Back to green, no semantic gaps with main (to the best of my knowledge). |
LGTM, I am locking forward to use ist soon. |
| 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; |
There was a problem hiding this comment.
Only this may be still a wrong git tag depending on the used copier argument!
There was a problem hiding this comment.
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.
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.
|
We're back to green, and there is now test coverage for the cmake module support (white box -- UUID changes). |
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:
copier.ymlcopier updatelaterWhy
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:
That lets older exemplar-based repositories be rebased onto a Copier-generated baseline and then participate in template updates going forward.
What changed
.copier-answers.ymltemplate so generated repositories keep Copier metadatastamp.shand local template validationParity
At this point the Copier-based workflow is functionally at parity with the prior cookiecutter-style stamping flow:
copier updatesupportSo 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