Repository navigation
Make header generation independent of filesystem case and git history - #11
Merged
Merged
Conversation
Two consumer-visible defects, both invisible on a Windows or macOS developer box. Template lookup was a bare EXISTS on the requested filename. That check is case-insensitive on Windows and macOS and case-sensitive on Linux, and the shipped template is Version.hpp.in while the README, the template's own header comment and the test suite all asked for version.hpp. The same project therefore produced constexpr constants on one platform and silently fell through to the C-macro template on another. Resolution now matches against the package directory listing, so it reports the spelling actually on disk and accepts either casing on every filesystem; two templates differing only in case stop the configure rather than picking one at random. VERSION_H_TEMPLATE was documented as the way to supply a template but the module overwrote it before use, so the only route left was writing into the package's own directory. The test suite did exactly that, and the artifact it wrote had been committed to the repository. It is now honoured as given, and a path that does not exist stops the configure instead of falling through to a different template. The no-history path configured cleanly and then could not be built: the generation target named .git/index and .git/HEAD as inputs unconditionally, and Ninja and Make refuse to build a target whose input no rule can produce. Only the bookkeeping files that exist are declared now. This also covers a repository whose first commit is not yet made, which has HEAD but no index. Diagnostics for the same path said the opposite of what happened. An untagged build, and a build outside a checkout, both printed a failure line and then generated a complete version anyway. Those two states are ordinary and the maintainer cannot act on either, so they close as passes that name the fallback; CHECK_FAIL is kept for the tag that matches VERSION_TAG_PATTERN but does not parse, which is a real misconfiguration. Shipping Version.h.in as a file rather than writing it at configure time removes the last caller of the "not found, generating" path for the default filename, which was the most visible of the false failure lines: it printed on every build of every project that took the defaults. Version.hpp.in gains VERSION_SEMANTIC alongside VERSION_STRING so the two templates agree on the macro name; VERSION_STRING stays for existing call sites. Tests: test_template_case pins the differently-cased request, test_custom_template now supplies a template from outside the package, test_no_git_repo pins the archive-with-no-.git build, and test_generated_compiles compiles and links a real target against the generated header. The suite passes the generator down to its sub-projects, which both halves its runtime and stops it silently testing a generator nobody uses. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The suite had no CI at all, so every run happened on a case-insensitive filesystem. A template referenced by a spelling that differs from the name on disk resolves silently there, which is why that defect survived in the module, in the README and in the suite's own fixtures at the same time. ubuntu-latest is the job that catches it; macos-latest is there because a consumer's build machine is as likely to be one. CMakePresets.json makes the "cmake --preset default && ctest --preset default" line the root CMakeLists already printed actually work. .gitattributes pins line endings in the repository so a contributor whose core.autocrlf is off cannot commit a whole-file ending change on top of a one-line edit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
add_dependencies takes no keyword arguments, so "INTERFACE" was parsed as a dependency name of its own. No target has that name; CMake tolerated it silently rather than reporting the unknown dependency. The ordering this line exists for is unaffected. Checked against the generated Ninja graph before and after: the consumer's object still carries "cmake_object_order_depends_target_consumer: phony || genCmakeVersion", so a target linking version::version still compiles after the header is generated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The same project produced different headers on Windows and on Linux, and nothing in either log said so.
Template lookup was a bare
EXISTSon the requested filename. That check is case-insensitive on Windows and macOS and case-sensitive on Linux. The shipped template isVersion.hpp.in, while the README, the template's own header comment and the test suite all asked forversion.hpp-- so a consumer asking for the C++ constexpr output got it on a developer box and silently fell through to the C-macro template on a Linux build machine. Resolution now matches against the package directory listing, reports the spelling actually on disk, and accepts either casing everywhere. Two templates differing only in case stop the configure rather than one being picked at random.VERSION_H_TEMPLATEwas documented as the way to supply your own template, but the module overwrote it before use. The only route left was writing into the package's own directory, which an installed or CPM-fetched copy is not the consumer's to write to -- the test suite did exactly that, and the artifact it wrote had been committed to this repository. It is honoured as given now:A path that does not exist stops the configure instead of falling through to a different template.
A tree with no git history configured cleanly and then could not be built at all: the generation target named
.git/indexand.git/HEADas inputs unconditionally, and Ninja and Make refuse to build a target whose input no rule can produce. Only the bookkeeping files that exist are declared now, which also covers a repository whose first commit is not yet made.Diagnostics on that same path said the opposite of what happened -- an untagged build, and a build outside a checkout, each printed a failure line and then generated a complete version anyway. Both are ordinary states the maintainer cannot act on, so they close as passes naming the fallback.
CHECK_FAILis kept for the tag that matchesVERSION_TAG_PATTERNbut does not parse, which is a real misconfiguration. ShippingVersion.h.inas a file rather than writing it at configure time removes the most visible of those false failure lines:Find 'Version.h.in' - Not Foundprinted on every build of every project that took the defaults.Functional testing:
ctestsuite grown from 8 cases to 11, 33/33 pass. Verified under both Ninja and Visual Studio on Windows. New cases pin the differently-cased request, a template supplied from outside the package, an archive with no.git, and a real executable that compiles and links against the generated header -- every prior case read the header as text, which cannot see a template emitting something the compiler rejects.CI: none existed, which is why a case-sensitivity defect survived simultaneously in the module, the README and the suite's own fixtures.
ubuntu-latestis the job that catches it;macos-latestis there because a consumer's build machine is as likely to be one.Reviewer notes:
version_resolveTemplateinCMake/Version.cmakeis the substance; the resolution order is documented above it._VERSION_PACKAGE_DIRis captured at file scope becauseCMAKE_CURRENT_LIST_DIRread inside a macro resolves against the list file that called it, not the one that defined it.tests/test_no_git_reposetsGIT_CEILING_DIRECTORIES; without it git ascends out of the build tree and describes this checkout.Risk: Low. Behaviour changes only where the previous behaviour was platform-dependent or produced an unbuildable target; the default path generates a byte-identical header.
🤖 Generated with Claude Code