Skip to content

Commit af21c71

Browse files
committed
ENH: Add migration sentinel maintenance tooling and documentation
Add MigrationSentinels.py with three subcommands: validate, which CI runs so a malformed sentinel cannot merge; expire-itk, which removes every sentinel at a tag because all of them ship in it; and expire-downstream, which reduces a consumer's two-arm condition to its version arm once the tag exists. validate also rejects a misspelled prefix. Such a file is invisible to the ITK_MIGRATION_*.md glob, so the sentinel would silently never be published. expire-downstream pins UTF-8 on the files it rewrites. The locale default is cp1252 on Windows, and the rewrite is in place and not atomic, so an unpinned encoding can silently corrupt non-ASCII content.
1 parent a5c5c75 commit af21c71

8 files changed

Lines changed: 425 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Migration sentinels
2+
3+
# A malformed sentinel is silently ignored by the CMake glob, so it would never
4+
# be published to downstream projects. Validate on every PR that touches one.
5+
6+
on:
7+
pull_request:
8+
paths:
9+
- 'CMake/MigrationSentinels/**'
10+
- 'Utilities/Maintenance/MigrationSentinels.py'
11+
- '.github/workflows/migration-sentinels.yml'
12+
13+
jobs:
14+
validate:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v6
18+
- uses: actions/setup-python@v6
19+
with:
20+
python-version: '3.12'
21+
- name: Validate migration sentinels
22+
run: |
23+
python3 Utilities/Maintenance/MigrationSentinels.py validate \
24+
|| {
25+
echo "::error::A migration sentinel is malformed. See" \
26+
"Documentation/docs/contributing/migration_sentinels.md"
27+
exit 1
28+
}

Documentation/docs/contributing/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ ITK Git Cheatsheet <https://github.com/InsightSoftwareConsortium/ITK/blob/main/D
468468
CDash Dashboard <https://open.cdash.org/index.php?project=Insight>
469469
dashboard.md
470470
updating_third_party.md
471+
migration_sentinels.md
471472
python_packaging.md
472473
../README.md
473474
```
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Migration sentinels
2+
3+
A migration sentinel lets a downstream project detect, at CMake configure time,
4+
that one specific ITK change is present — during the interval between ITK tags,
5+
which can run two years.
6+
7+
The motivating case is `git bisect`. A downstream project bisecting ITK to find
8+
which change broke it is configured against a sequence of arbitrary commits, and
9+
at each step must choose which baseline images to test against. Sentinels are
10+
plain files in the source tree, so they are readable in a detached `HEAD`, a
11+
shallow clone, or an exported archive — states in which `git describe` fails and
12+
`git rev-list --count` silently returns a wrong answer.
13+
14+
Sentinels are **not** a general feature-detection API. They are removed at each
15+
tag. Sentinels are intended **only** for fine-grained configure-time decisions
16+
between tagged releases.
17+
18+
## Adding a sentinel
19+
20+
Add one file to `CMake/MigrationSentinels/` containing a single line of
21+
description:
22+
23+
```
24+
CMake/MigrationSentinels/ITK_MIGRATION_PR6532.md
25+
```
26+
27+
```markdown
28+
itk::Math::SVD replaces vnl_svd for ITK-internal consumers; see PR #6532.
29+
```
30+
31+
Choose exactly one naming pattern:
32+
33+
| Pattern | When |
34+
|---|---|
35+
| `ITK_MIGRATION_PR<N>` | **Strongly preferred** — traceable to the pull request. |
36+
| `ITK_MIGRATION_<DESCRIPTIVE_NAME>` | No single PR captures the change. |
37+
| `ITK_MIGRATION_HASH_<githash>` | Only a commit identifies the change. |
38+
39+
CI rejects a name matching none of these. It also rejects a misspelled prefix,
40+
which would otherwise be silently ignored by the glob and never published.
41+
42+
## Using a sentinel downstream
43+
44+
```cmake
45+
find_package(ITK REQUIRED)
46+
47+
if(ITK_VERSION VERSION_GREATER_EQUAL 6.1.0
48+
OR ITK_MIGRATION_PR6532 IN_LIST ITK_MIGRATION_SENTINELS)
49+
set(BASELINE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Baseline/post-6532)
50+
else()
51+
set(BASELINE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Baseline/legacy)
52+
endif()
53+
```
54+
55+
Name the ITK release you expect to contain the change as the version arm. Both
56+
arms are required:
57+
58+
- The **sentinel arm** is true between tags, before any release contains it.
59+
- The **version arm** is true after the tag, once sentinels have been expired.
60+
61+
Against an ITK predating this mechanism, `ITK_MIGRATION_SENTINELS` is unset,
62+
`IN_LIST` is false, and the legacy branch is taken — no guard needed. This is
63+
also why the mechanism is a list variable rather than a function: calling a
64+
function that older ITK does not define is a hard `Unknown CMake command`
65+
error, and CMake's `if()` cannot invoke a function anyway.
66+
67+
## Expiring at a tag
68+
69+
Run once after tagging:
70+
71+
```bash
72+
python3 Utilities/Maintenance/MigrationSentinels.py expire-itk
73+
python3 Utilities/Maintenance/MigrationSentinels.py \
74+
expire-downstream --tag 6.1.0 --repo ../BRAINSTools
75+
```
76+
77+
Order does not matter and neither step is urgent. A downstream not yet rewritten
78+
keeps working against the tagged ITK because the version arm is now true.
79+
80+
## Why this is not in a compiled header
81+
82+
`ITK_MIGRATION_SENTINELS` is exported through `ITKConfig.cmake` only. Embedding
83+
it in `itkConfigure.h` would change every translation unit's preprocessed output
84+
on most merges and destroy ccache hit rates on every build machine. The
85+
`MigrationSentinelConfigureHeaderGuard` test enforces this.

0 commit comments

Comments
 (0)