From 2e2c3bd0ada18710c8f2ab781e2dfb8bef3fdd44 Mon Sep 17 00:00:00 2001 From: Hans Johnson Date: Tue, 21 Jul 2026 13:19:58 -0500 Subject: [PATCH] ENH: Auto-compute ITK_VERSION_TWEAK from git commit date Set the fourth (tweak) version component at configure time to the YYYYMMDD date of the most recent commit, instead of a hand-edited date. The computation is pure CMake, so a native build gains no interpreter dependency: `.git_archival.txt` is read first, so an exported tarball -- or a fork that commits a substituted file -- pins the date it declares, and a working tree falls through to `git log`, which succeeds even in a shallow or tagless clone. ITK_VERSION_TWEAK and ITK_VERSION_FULL are exported through ITKConfig.cmake for downstream between-tag gating. The value never reaches itkConfigure.h, so the daily change cannot invalidate ccache. Supersedes the manual-file approach of #6667. Co-Authored-By: Bradley Lowekamp <321061+blowekamp@users.noreply.github.com> --- .git_archival.txt | 4 ++ .gitattributes | 3 + CMake/ITKConfig.cmake.in | 7 +++ CMake/ITKConfigVersion.cmake.in | 11 +++- CMake/itkVersionTweak.cmake | 63 ++++++++++++++++++++ CMakeLists.txt | 5 ++ Documentation/docs/migration_guides/index.md | 28 +++++++++ 7 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 .git_archival.txt create mode 100644 CMake/itkVersionTweak.cmake diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 00000000000..8d015c81d1a --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=v[0-9]*)$ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes index 862ce69f060..68dd23782a8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,9 @@ .git* export-ignore .hooks* export-ignore .mailmap export-ignore +# Override the .git* export-ignore above: this file MUST ship in archives, and +# git substitutes the describe string into it so tarballs compute the tweak. +.git_archival.txt -export-ignore export-subst *.bat -crlf *.bin -crlf diff --git a/CMake/ITKConfig.cmake.in b/CMake/ITKConfig.cmake.in index 367b1b096ae..ec0e05a093b 100644 --- a/CMake/ITKConfig.cmake.in +++ b/CMake/ITKConfig.cmake.in @@ -25,6 +25,13 @@ set(ITK_REQUIRED_LINK_FLAGS "@ITK_REQUIRED_LINK_FLAGS@") set(ITK_VERSION_MAJOR "@ITK_VERSION_MAJOR@") set(ITK_VERSION_MINOR "@ITK_VERSION_MINOR@") set(ITK_VERSION_PATCH "@ITK_VERSION_PATCH@") +# Tweak is the YYYYMMDD date of the most recent commit; enables fine-grained +# downstream gating between release tags via ITK_VERSION_FULL. +set(ITK_VERSION_TWEAK "@ITK_VERSION_TWEAK@") +set( + ITK_VERSION_FULL + "@ITK_VERSION_MAJOR@.@ITK_VERSION_MINOR@.@ITK_VERSION_PATCH@.@ITK_VERSION_TWEAK@" +) # Remove all legacy code completely. set(ITK_LEGACY_REMOVE "@ITK_LEGACY_REMOVE@") diff --git a/CMake/ITKConfigVersion.cmake.in b/CMake/ITKConfigVersion.cmake.in index 5ce84d5cbcb..5b869e32cb2 100644 --- a/CMake/ITKConfigVersion.cmake.in +++ b/CMake/ITKConfigVersion.cmake.in @@ -1,7 +1,14 @@ -set(PACKAGE_VERSION "@ITK_VERSION_MAJOR@.@ITK_VERSION_MINOR@.@ITK_VERSION_PATCH@") +set( + PACKAGE_VERSION + "@ITK_VERSION_MAJOR@.@ITK_VERSION_MINOR@.@ITK_VERSION_PATCH@.@ITK_VERSION_TWEAK@" +) if(NOT "${PACKAGE_FIND_VERSION}" VERSION_GREATER "${PACKAGE_VERSION}") set(PACKAGE_VERSION_COMPATIBLE 1) # compatible with older - if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "${PACKAGE_VERSION}") + if( + "${PACKAGE_FIND_VERSION}" VERSION_EQUAL "${PACKAGE_VERSION}" + OR "${PACKAGE_FIND_VERSION}" VERSION_EQUAL + "@ITK_VERSION_MAJOR@.@ITK_VERSION_MINOR@.@ITK_VERSION_PATCH@" + ) set(PACKAGE_VERSION_EXACT 1) # exact match for this version endif() endif() diff --git a/CMake/itkVersionTweak.cmake b/CMake/itkVersionTweak.cmake new file mode 100644 index 00000000000..35796cca7ea --- /dev/null +++ b/CMake/itkVersionTweak.cmake @@ -0,0 +1,63 @@ +# Compute ITK_VERSION_TWEAK: the YYYYMMDD date of the most recent commit, at +# configure time. The tweak must never reach itkConfigure.h, so a per-commit +# change of this value cannot invalidate ccache. +# +# .git_archival.txt is consulted before git so that an archive, or a fork that +# commits a substituted file, pins the date it declares. In this repository the +# file holds unexpanded $Format:$ placeholders, so a working tree falls through +# to `git log`, which succeeds even in a shallow or tagless clone. +function(_itk_version_tweak_from_date date outvar) + if(date MATCHES "([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])") + set( + ${outvar} + "${CMAKE_MATCH_1}${CMAKE_MATCH_2}${CMAKE_MATCH_3}" + PARENT_SCOPE + ) + else() + set(${outvar} "" PARENT_SCOPE) + endif() +endfunction() + +function(_itk_compute_version_tweak outvar) + set(tweak "") + + if(EXISTS "${ITK_SOURCE_DIR}/.git_archival.txt") + file( + STRINGS + "${ITK_SOURCE_DIR}/.git_archival.txt" + archival + REGEX "^node-date:" + ) + if(archival) + list(GET archival 0 node_date) + # Unexpanded placeholders mean this is a checkout, not an archive. + if(NOT node_date MATCHES "\\$Format:") + _itk_version_tweak_from_date("${node_date}" tweak) + endif() + endif() + endif() + + if(NOT tweak MATCHES "^[0-9]+$") + find_package(Git QUIET) + if(Git_FOUND) + execute_process( + COMMAND + "${GIT_EXECUTABLE}" -C "${ITK_SOURCE_DIR}" log -1 --format=%cd + --date=short + OUTPUT_VARIABLE commit_date + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + _itk_version_tweak_from_date("${commit_date}" tweak) + endif() + endif() + + # Neither archive metadata nor git history, e.g. an unpacked plain tarball. + if(NOT tweak MATCHES "^[0-9]+$") + set(tweak "0") + endif() + set(${outvar} "${tweak}" PARENT_SCOPE) +endfunction() + +_itk_compute_version_tweak(ITK_VERSION_TWEAK) +set(ITK_VERSION_FULL "${ITK_VERSION}.${ITK_VERSION_TWEAK}") diff --git a/CMakeLists.txt b/CMakeLists.txt index d34521cf0bd..cfbfc95513f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,11 @@ project( C ) +# Fourth (tweak) version component, auto-computed as the YYYYMMDD date of the +# most recent commit. Enables downstream between-tag gating via +# ITK_VERSION_FULL; see Documentation/docs/migration_guides/index.md. +include(itkVersionTweak) + # Required as a cache variable for FetchContent parent projects can # resolve ITK's CMake macros and config paths. set( diff --git a/Documentation/docs/migration_guides/index.md b/Documentation/docs/migration_guides/index.md index 7af7696e24d..6d3d78fdab6 100644 --- a/Documentation/docs/migration_guides/index.md +++ b/Documentation/docs/migration_guides/index.md @@ -2,6 +2,34 @@ These migration guides explain how to update major versions of ITK, whic may contain breaking changes to the API. +## Fine-grained versioning between release tags + +ITK's fourth (tweak) version component is the `YYYYMMDD` date of the most +recent commit, computed automatically at configure time. It exists so +downstream projects building against untagged ITK commits — for example during +a `git bisect` — can make configure-time decisions at day granularity between +release tags: + +```cmake +find_package(ITK REQUIRED) +if(ITK_VERSION_FULL VERSION_GREATER_EQUAL 6.0.0.20260722) + set(BASELINE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Baseline/post-change) +else() + set(BASELINE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Baseline/legacy) +endif() +``` + +`ITK_VERSION_FULL` and `ITK_VERSION_TWEAK` are exported through +`ITKConfig.cmake`. Against an older ITK that predates the tweak component, +`ITK_VERSION_FULL` is unset and the comparison is false, so the legacy branch +is taken. + +The date comes from `git log` in a working tree — including a shallow or +tagless clone — and from the `git archive`-substituted `.git_archival.txt` in +an exported source tarball; it falls back to `0` only when neither git history +nor that substitution is available. No manual edits are needed — the tweak +advances with every commit. + ```{toctree} :hidden: :maxdepth: 3