Conversation
The version number was written by hand in six files: CMakeLists.txt, src/version.hpp, the two Python tools and the two conf.py of the manual. tenes --version printed the one of version.hpp and did not look at CMakeLists.txt, and every build of develop and of a feature branch printed the same "2.2-dev", whatever it was built from. set(TENES_VERSION ...) in the top-level CMakeLists.txt is now the only place. tenes, tenes_simple and tenes_std print it followed by the commit, "TeNeS v2.2-dev (06780ea)": the first 8 digits of the hash, which is what git rev-parse --short prints in this repository, and "-dirty" for a tree with uncommitted changes to tracked files. The manual shows the number alone; conf.py reads it from CMakeLists.txt. Where the commit comes from: - a git checkout asks git, at every build and not at configuration, since a commit changes the hash without a new configuration. version.cpp and the tools are rewritten only when their content changes, so a build with nothing new compiles and links nothing; - a tarball of git archive, which is what GitHub serves as the source archive, carries it in config/git_archive.txt (export-subst). It is %H and not %(describe): the tags of this repository are lightweight, and the hash does not depend on the version of git; - any other copy of the sources prints the number alone. When git cannot read the checkout, as under "sudo make install" where it refuses the repository of another user, the values of the last build are kept. A Python tool run from the source tree, without CMake, looks the number and the commit up when --version is asked for; one copied out of the tree prints "unknown" rather than the commit of whatever repository it landed in. The values live in a generated version.cpp and version.hpp only declares them: a generated header would lose against the stale src/version.hpp of an earlier checkout in #include "version.hpp", and would drop out of the installed headers. TENES_VERSION stays as a macro but is a function call now, not a string literal. output/timers.json records the commit as git_commit (the full hash) and git_dirty, null when the commit is not known. The shebang of the built tools is written by the same CMake script; the default interpreter "/usr/bin/env python3" is a list of two elements for CMake and is joined before it is passed on. ctest 47/47 (Apple clang, no MPI, Release) with a display of 7 digits; after the change to 8 the tests that see it were run again (test_timer_registry, python_unittest and two integration tests). tenes, tenes_std and tenes_simple print the same commit when built from a checkout and from a git archive tarball. Not tried: an MPI build, Ninja, and an archive downloaded from GitHub itself. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With CMake 3 the build stopped at version.cpp:
bool git_dirty() { return @TENES_GIT_DIRTY@; }
config/write_version.cmake is run with cmake -P, and a script starts with
no policy set. CMake 3 then keeps the old behaviour of CMP0053, which
expands @var@ inside a quoted argument. The pattern "@TENES_GIT_DIRTY@" of
string(REPLACE) became the value of the variable, "false", and nothing was
replaced; the same for the version number and the commit, which sit in
string literals and compiled, so tenes would have printed "@TENES_VERSION@"
had the third not been a bool. CMake 4 has no old behaviour left, which is
why it did not show where this was written.
The script now sets the policies, and the patterns are bracket arguments,
which no version of CMake evaluates. It also stops when a placeholder
@TENES_...@ is left in what it writes.
A placeholder left in a Python tool does not stop the build: the tool
falls back to looking the version up at run time and, installed, prints
"unknown". version_tenes, version_tenes_simple and version_tenes_std run
the built programs with --version and compare with the version number of
CMakeLists.txt.
Checked with CMake 4.4.1 only; CMake 3 is what the CI has.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
yomichi
left a comment
There was a problem hiding this comment.
Thanks. I checked that the hash comes out right from a checkout, a worktree and a git archive tarball, that -dirty follows tracked files only, and that a second build compiles and links nothing (Makefiles and Ninja). A few points:
1. No test fails when the commit is no longer embedded.
In a git checkout, the hash is optional everywhere it is checked:
test/CMakeLists.txt:( \([0-9a-f]+(-dirty)?\))?forversion_*,test/timer_registry.cpp: thehash.empty()branch of "version_string is the version number followed by the commit",test/python/test_version.py:test_formatandtest_version_option.
With config/git_hash.cmake changed to always return an empty hash, tenes --version prints TeNeS v2.2-dev, and version_tenes, version_tenes_simple, version_tenes_std and test_timer_registry still pass. With the runtime lookup of the Python tools changed to return "", all 18 cases of test_version.py still pass. Could the tests require the hash when the source tree is a git checkout (for example, by choosing the regex at configure time from whether ${TeNeS_SOURCE_DIR}/.git exists)?
2. (Minor) When git cannot read the checkout, the whole output is kept.
config/write_version.cmake returns before it reads INPUT when TENES_GIT_FAILED is set and OUTPUT exists (lines 15-18). Since this script is also the only path from tool/tenes_std.py to build/tool/tenes_std, an edit to a tool or a new TENES_VERSION then silently does not reach the build. This needs a checkout that git cannot read at build time (e.g. built in a container as another user), so it is rare, but keeping only the hash and the dirty flag of the last build, and always filling in INPUT, would avoid it.
3. (Minor) sudo make install and git status.
- The comment in
write_version.cmakeand the description say that git refuses the repository undersudo make install. Current git allows it: under sudo,safe.directoryaccepts a repository owned bySUDO_UID(seegit help config, written for "make && sudo make install"). So that case does not take theTENES_GIT_FAILEDbranch, and the comment could name another case. git statusrefreshes.git/indexwhen the stat data is stale, so a build can takeindex.lockwhile the user runs git.git --no-optional-locks status(inconfig/git_hash.cmakeand in the two tools) avoids this, and is what git recommends for tools that run in the background.
Not tested: an MPI build, CMake 3.8 itself.
Review of #122. 1. No test failed when the commit was no longer embedded. The commit is legitimately absent in a copy of the sources that is neither a checkout nor a tarball of git archive, so every check made it optional: the regex of version_*, the hash.empty() branch in timer_registry.cpp, test_format and test_version_option. With config/git_hash.cmake returning no commit, or with the lookup of the tools doing so, all of them passed. What to expect is now decided apart from the code under test, by asking git: test/CMakeLists.txt runs git rev-parse at configuration (or reads config/git_archive.txt in a tarball) and requires the 8 digits, or their absence; it passes the same to test_timer_registry. The Python tests ask git when they run and compare the digits themselves. Asking config/git_hash.cmake would not do: a script that finds no commit would be expected to find none. 2. When git could not read the checkout, config/write_version.cmake returned before reading its input and kept the whole output of the last build. It is also the only way from tool/tenes_std.py to the built tool, so an edit to a tool, or a new version number, did not reach the build. It now keeps the commit only: the last commit git gave is written beside the output and filled in when git fails; the input and the version number are always those of the build. 3. "sudo make install" was given as the case in which git refuses the checkout. It is not one: under sudo git accepts the repository of the user who ran it (safe.directory in git-config). The comment names a checkout owned by another user, as in a container. git status may refresh the index and take its lock, in the way of a git command the user runs during a build. It is run with --no-optional-locks, by config/git_hash.cmake and by the two tools. Also, a tool run from the source tree dropped the commit when git status failed after git rev-parse had succeeded; config/git_hash.cmake keeps it and reports the tree as clean, and the tools do the same now. Both mutations of the review turn the tests red: no commit from config/git_hash.cmake fails version_tenes, version_tenes_simple, version_tenes_std, test_timer_registry and python_unittest; no commit from the lookup of the tools fails eight cases of test_version.py. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Summary
The version number was written by hand in six files, and
tenes --versionprinted the same2.2-devfor every build ofdevelopand of any feature branch.This PR makes
set(TENES_VERSION ...)in the top-levelCMakeLists.txtthe only place where the number is written, and makes--versionprint the commit the program was built from.Changes
--versionoftenes,tenes_simpleandtenes_stdprints the version number followed by the first 8 digits of the commit hash, and-dirtywhen the source tree had uncommitted changes to tracked files.output/timers.jsonrecords the commit inmetaasgit_commit(the full hash) andgit_dirty. Both arenullwhen the commit is not known.conf.pyreads it fromCMakeLists.txt.src/version.hpp, the two Python tools and the twoconf.pyno longer carry a copy.Where the commit comes from
git archive(the source archives of GitHub)config/git_archive.txt, filled in throughexport-substNotes on the choices:
version.cppand the built tools are rewritten only when their content changes, so a build with nothing new compiles and links nothing.%H, not%(describe). The tags of this repository are lightweight, and the hash does not depend on the version of git.sudo make install, where git refuses the repository of another user), the values of the last build are kept.--versionis asked for. A tool copied out of the tree printsunknownrather than the commit of whatever repository it landed in.Compatibility
TENES_VERSIONinversion.hppis still a macro, but it now expands to a function call,tenes::version(), and is no longer a string literal. Code that concatenates it as a literal ("v" TENES_VERSION) has to be changed.version.hppis an installed header.timers_to_json()takes two more arguments (git_commit,git_dirty).version.cpp;version.hpponly declares them. A generated header would lose against a stalesrc/version.hppin#include "version.hpp", and would drop out of the installed headers.Also fixed
The shebang of the built tools is now written by the same CMake script. The default interpreter,
/usr/bin/env python3, is a list of two elements for CMake and is joined before it is passed on.Release procedure
Tags are created at release time and are kept equal to
TENES_VERSIONby hand. No check in CI is added.Tests
ctest: 77/77 (Apple clang, no MPI, Release).test/python/test_version.py(18 cases), and two cases intest/timer_registry.cpp.tenes,tenes_stdandtenes_simpleprint the same commit when built from a git checkout and from agit archivetarball. This was checked before the rebase onto the currentdevelop; the build from a checkout was checked again after it.TeNeS 2.2-dev documentation(checked before the rebase).Not tested:
git archive).Notes for reviewers
git rev-parse --shortprints in this repository.mptensor,toml11), so a tarball alone still cannot be built. This PR does not change that.🤖 Generated with Claude Code