* Extensions for testing Generalize run-tests.sh Test all C++ versions at once Fix combination of big endian and -Wsign-conversion Failed on s390x (as reference for big endian) Add github workflow for s390x Add armhf container files Devcontainers for i386 and riscv Add github workflows for armhf, i386 and riscv64 Add run-tests.sh for foreign architectures Document testing in doc/testing.md Adjustments from clang-format run Fix .devcontainer/s390x/Dockerfile for linebreak syntax Fix exit code of run-test.sh Previously, "exit $?" was used, actually the return value of FailedCompilation and FailedTest which are always 0. Now just using 1. In run-tests.sh at ctest, use -V for printing number of tests unconditionally While ctest suppresses individual test list by default, it didn't even print the number of tests anymore, as run_tests.sh does because it suppresses it output completely. Now, by default print number of tests, and in verbose mode, print test list in addition. * Support powerpc as foreign architecture * Add SFINAE constraints to etl::begin/end and reverse iterator free functions The unconstrained etl::begin(), etl::end(), etl::cbegin(), etl::cend(), etl::rbegin(), etl::rend(), etl::crbegin(), and etl::crend() templates in the no-STL code path were matching iterator types during ADL, causing a hard error with GCC 15's std::ranges::begin. When std::ranges performed ADL on an etl::*::iterator, it found etl::begin() as a candidate; since the iterator type has a nested iterator typedef, the return type TContainer::iterator was valid, but calling .begin() on the iterator failed. Fix: add etl::void_t<decltype(...)> SFINAE guards to each template, ensuring they only participate in overload resolution when TContainer actually has the corresponding member function (.begin(), .end(), etc.). * - Fix red unit tests on 32 bits big-endian platform. * Document powerpc architecture for testing * Use Dockerfiles in cross testing github workflows Synchronizes environment setup for github workflows to what is defined in the development Dockerfiles. So they don't need to be maintained separately. --------- Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com> Co-authored-by: Sergei Shirokov <sergej.shirokov@gmail.com> Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>
5.9 KiB
Code Generation for Pre-C++11 Support
ETL supports C++03 (also referred to as C++98) environments where variadic
templates, constexpr, and other modern features are unavailable. To
provide equivalent functionality, certain headers are generated using
Cog, a Python-based code generation
tool that embeds Python snippets inside source files.
This document explains how the code generation system works and how to regenerate the headers if you modify a generator template.
Overview
| Directory | Contents |
|---|---|
include/etl/generators/ |
Generator templates (*_generator.h) and batch scripts |
include/etl/private/ |
Generated output (*_cpp03.h) committed to the repository |
scripts/generator_test.py |
CI script that verifies generators match committed files |
The generator templates contain embedded Python code (delimited by [[[cog
and ]]]) that produces the repetitive C++03 boilerplate. Cog processes
these templates and writes the expanded output to include/etl/private/.
Generated Headers
The following C++03 compatibility headers are generated:
| Generator | Output | Purpose |
|---|---|---|
fsm_fwd_decl_cpp03_generator.h |
fsm_fwd_decl_cpp03.h |
FSM forward declarations |
fsm_friend_decl_cpp03_generator.h |
fsm_friend_decl_cpp03.h |
FSM friend declarations |
fsm_cpp03_generator.h |
fsm_cpp03.h |
Finite state machine implementation |
message_router_cpp03_generator.h |
message_router_cpp03.h |
Message router |
message_packet_cpp03_generator.h |
message_packet_cpp03.h |
Message packet |
largest_type_cpp03_generator.h |
largest_type_cpp03.h |
Largest type metafunction |
largest_alignment_cpp03_generator.h |
largest_alignment_cpp03.h |
Largest alignment metafunction |
largest_cpp03_generator.h |
largest_cpp03.h |
Largest type/size utilities |
smallest_cpp03_generator.h |
smallest_cpp03.h |
Smallest type/size utilities |
type_traits_cpp03_generator.h |
type_traits_cpp03.h |
Type traits (is_one_of, etc.) |
type_lookup_cpp03_generator.h |
type_lookup_cpp03.h |
Type lookup metafunction |
type_select_cpp03_generator.h |
type_select_cpp03.h |
Type selection metafunction |
variant_pool_cpp03_generator.h |
variant_pool_cpp03.h |
Variant pool |
Generator Parameters
Cog variables control how many template parameter overloads are generated:
| Variable | Default | Used by |
|---|---|---|
Handlers |
16 | FSM and message router generators |
NTypes |
16 | Type utility generators (largest, smallest, lookup, select, variant pool) |
IsOneOf |
16 | Type traits generator (is_one_of) |
These defaults produce overloads supporting up to 16 types or handlers, which is sufficient for most embedded applications while keeping compile times reasonable.
Prerequisites
-
Python 3
-
cogapp – install via:
pip install cogapp
Regenerating Headers
Using the batch scripts (Windows)
Each generator has a corresponding .bat file in include/etl/generators/:
cd include/etl/generators
generate.bat # Regenerate all headers
generate_fsm.bat # Regenerate FSM headers only
generate_smallest.bat # Regenerate smallest_cpp03.h only
# etc.
Manual invocation
Run Cog directly from the include/etl/generators/ directory:
cd include/etl/generators
# Example: regenerate smallest_cpp03.h
python3 -m cogapp -d -e -o../private/smallest_cpp03.h -DNTypes=16 smallest_cpp03_generator.h
# Example: regenerate fsm_cpp03.h
python3 -m cogapp -d -e -o../private/fsm_cpp03.h -DHandlers=16 fsm_cpp03_generator.h
Cog options used:
| Option | Meaning |
|---|---|
-d |
Delete the generator markers from output |
-e |
Warn if the input file has no generator markers |
-o<file> |
Write output to the specified file |
-D<var>=<value> |
Define a Cog variable |
Regenerating all headers
The generate.bat script regenerates every header:
cd include/etl/generators
./generate.bat # Windows
# or run the commands manually on Linux/macOS
On Linux/macOS you can run the commands from generate.bat directly in
your shell (they are standard python3 -m cogapp invocations).
Verifying Generators
After modifying a generator template, verify the output matches the committed file:
python3 scripts/generator_test.py
This script:
- Runs Cog on every
*_generator.hfile. - Compares each output against the corresponding file in
include/etl/private/. - Reports success or failure.
The generator.yml GitHub Actions workflow runs this automatically on
every push and pull request.
How Generators Work
A generator template contains standard C++ code interspersed with Cog
directives. For example, from smallest_cpp03_generator.h:
/*[[[cog
import cog
cog.outl("template <typename T1, ")
for n in range(2, int(NTypes)):
cog.out("typename T%s = void, " % n)
cog.outl("typename T%s = void>" % int(NTypes))
]]]*/
// Generated code appears here after running Cog
/*[[[end]]]*/
When Cog processes this file with -DNTypes=16, the Python code executes
and outputs the expanded template parameter list supporting 16 types.
Adding a New Generator
- Create
include/etl/generators/<name>_cpp03_generator.hwith Cog directives. - Add a corresponding entry to
generate.bat. - Run
generate.bat(or the equivalent Cog command) to produceinclude/etl/private/<name>_cpp03.h. - Commit both the generator and the generated output.
- Verify with
python3 scripts/generator_test.py.
Troubleshooting
| Problem | Solution |
|---|---|
ModuleNotFoundError: No module named 'cogapp' |
Install Cog: pip install cogapp |
| Generator output differs from committed file | Regenerate and commit the updated output |
| Need more than 16 types/handlers | Change -DNTypes= or -DHandlers= and regenerate |