Repository navigation
cmake: find the Python3 interpreter before the first add_test uses it, and pin the order (fixes #1864) - #1891
Conversation
…, and pin the order (fixes #1864) `find_package(Python3 COMPONENTS Interpreter)` sat at :761 while **nine** registrations above it expanded `${Python3_EXECUTABLE}` to the empty string, so `CTestTestfile.cmake` held e.g. ``` add_test([=[hrx_build_scope]=] "<src>/tools/ci/hrx_build_scope.py" "--self-test") ``` -- the script as the program. Those tests ran only through their **exec bit**, which is why a mode-only rewrite of `tools/ci/hrx_build_scope.py` (100755 -> 100644, no content change) turned the row into `BAD_COMMAND`. The interpreter is now found once, above the first user, and the registrations go through one place: ```cmake find_package(Python3 COMPONENTS Interpreter) function(add_python_test name) if(Python3_Interpreter_FOUND) add_test(NAME ${name} COMMAND ${Python3_EXECUTABLE} ${ARGN}) else() message(STATUS "ctest: not registering ${name} (no Python3 interpreter)") endif() endfunction() ``` Nine registrations used the variable and now use the helper; the second `find_package` (and its now-redundant guard comment) is gone, since the later block is still guarded by `Python3_Interpreter_FOUND`. **Why a helper and not just moving the find_package:** moving it fixes today's nine and leaves the next `add_test` above it free to repeat the bug, and it does nothing for a machine without Python3, where an empty interpreter makes ctest hand a `.py` to the kernel as a program. The helper makes both impossible by construction. ## The rule is now checked, on the file, with no configure `tests/python_test_order_check.py` (`--check --root`, `--self-test`) requires every `${Python3_EXECUTABLE}` use to follow the first `find_package(Python3` in each `CMakeLists.txt`, and is registered twice like the other `*_check.py` pairs. Against `origin/main` it reports **exactly the nine the issue counted**: ``` main offenders: [282, 283, 287, 288, 295, 303, 705, 707, 712] ``` and against this head, none. Its own comment mentions the variable, so the checker strips comments first -- with a self-test case for that, because a checker that trips on the prose explaining it would be switched off in a week. ## Verified ``` cmake -S . -B <build> -> rc 0 CTestTestfile.cmake BEFORE: add_test([=[hrx_build_scope]=] "<src>/tools/ci/hrx_build_scope.py" "--self-test") CTestTestfile.cmake AFTER : add_test([=[hrx_build_scope]=] "/home/bcloud/.local/bin/python3.14" "<src>/... " "--self-test") chmod -x tools/ci/hrx_build_scope.py; <script> --self-test -> "Permission denied", rc 126 (the pre-fix form: what ctest reported as BAD_COMMAND) chmod -x tools/ci/hrx_build_scope.py; ctest -R hrx_build_scope -> 100% tests passed, 0 failed out of 1 (through the interpreter) ctest -R 'python_test_order|ci_targets|tests_referenced|device_holders|moe_placement|hrx_build_scope|copyright_check_untracked|fa_ratio' -> 100% tests passed, 0 failed out of 11 python3 tests/python_test_order_check.py --self-test -> self-test ok python3 tools/copyright.py --check (staged) -> rc 0 ``` Fixes #1864.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 16b0924 — the bug is real, the fix is the right one, and its own checker demonstrates both directions.
The defect, measured on main
CMakeLists.txt: the first ${Python3_EXECUTABLE} use is at :282, find_package(Python3 …) at :761 — so every registration above 761 expanded to an empty interpreter, ctest ran the .py file directly, and that works only while the file keeps its exec bit. A mode-only rewrite of tools/ci/hrx_build_scope.py turning that row into BAD_COMMAND is exactly the shape that would report a script problem as a missing program.
Its checker, run against main and against this head
| tree | result |
|---|---|
origin/main's CMakeLists.txt |
rc 1, FAIL CMakeLists.txt:705/707/712: ${Python3_EXECUTABLE} used before find_package(Python3) and "9 registration(s) run a script through its exec bit instead of through the interpreter" |
| this PR's tree | rc 0 — "every ${Python3_EXECUTABLE} use follows find_package(Python3)" |
--self-test |
rc 0 |
That is a reviewer's ideal: the new row fails on the bug it was written for and passes on the fix, and I did not have to trust the comment for it.
The registration of the checker itself, which is where these usually die
add_python_test(python_test_order_check ${CMAKE_SOURCE_DIR}/tests/python_test_order_check.py --check --root ${CMAKE_SOURCE_DIR})
add_python_test(python_test_order_check_self ${CMAKE_SOURCE_DIR}/tests/python_test_order_check.py --self-test)add_python_test is the new helper, so the checker runs through the interpreter like everything else it governs — no exec-bit dependency introduced by the fix for the exec-bit dependency. Eleven registrations now go through it.
One tradeoff worth stating, not objecting to
On a machine with no Python3, add_python_test drops the test with a STATUS line rather than registering a broken row. That is better than BAD_COMMAND, and it is a real choice: those tests then silently do not run there. The STATUS line is the only trace, and ctest shows nothing. Since this repo has just spent a night on "a test nothing runs", the alternative (a FAIL-on-configure message) would be louder — but the comment says which one is intended, and a box that runs this suite has Python3 by construction.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 16b0924: READY
Checked at 16b0924 (CMakeLists.txt +28 −14, tests/python_test_order_check.py +123; MERGEABLE). I configured this head in an existing build dir:
CTestTestfile.cmakebefore and after, same build dir:- on
main:add_test([=[hrx_build_scope]=] "<src>/tools/ci/hrx_build_scope.py" "--self-test"), the script as the program; - at this head:
… "/home/bcloud/.local/bin/python3.14" "<src>/tools/ci/hrx_build_scope.py" ….
- on
- The defect's own scenario: with
chmod -x tools/ci/hrx_build_scope.py,ctest -R '^hrx_build_scope$'still Passed. That mode-only change is what turned the row intoBAD_COMMAND. Restored. - The checker:
--self-testok. On this head, "every ${Python3_EXECUTABLE} use follows find_package(Python3)", rc 0. On an extractedorigin/main, "9 registration(s) run a script through its exec bit", matching the issue's count. - The 9 Python rows (
python_test_order_check{,_self},ci_targets{,_self},tests_referenced{,_self},device_holders,moe_placement,hrx_build_scope) all pass.
The helper is the better shape than moving the find_package alone, for the reason the description gives: it covers the next add_test above it, and the no-interpreter machine.
Coordinate: #1866 fixes the same #1864 and conflicts with this in CMakeLists.txt. Its reviews at 9c8215b call it "the canonical of the two", but that was written against #1865, which is closed, not this PR. This one is a superset: the same move, plus the helper and the ordering check. I'd land this and close #1866, unless #1866's author prefers to fold the checker into theirs.
|
VERDICT: READY at The defect is worse than "a warning": nine registrations above
Three asks:
Coordination: |
|
VERDICT: READY This is the fix for engine#1864, and I verified it the only way that counts — by running its checker against both trees: and Two things here are better than the minimal fix:
Also: One nit: the message |
|
VERDICT: READY at What makes this one differentif(Python3_Interpreter_FOUND)
…
else()
message(STATUS "ctest: not registering ${name} (no Python3 interpreter)")
endif()
# engine#1864: the ordering rule itself, checked on the file (no configure, no build): every ${Python3_EXECUTABLE} us[age]
add_python_test(python_test_order_check … tests/python_test_order_check.py --check --root …)The other two fix the instance; this one removes the class twice over:
That second one is the difference between a fix and a ratchet. Given how this class behaved (silent for weeks, then misdiagnosed as a permissions problem), a check that reads the file is worth more than the reordering it protects. Recommendation across the threeMerge this one; close #1865 and #1866 with pointers. #1866's comment carries the best explanation of the incident ( (My earlier recommendation was "keep #1866" — that was made before this PR existed, and it had no guard or check.) |
|
VERDICT: READY at The defect, verified by running the checker on both trees
That is the same class as The checker is the right shape, and it has its own self-testBoth directions in the self-test, which is what makes the Scope
One ask: after this lands, the build log's test list should be identical in count (none added or removed) — worth one line in the body, since a reader seeing nine registrations change will want to know that the number did not move. And the same |
…after find_package(Python3), offsets from one text (refs #1979; lands #2012 + #2072) (#2123) * python_test_order_check: an include that binds the interpreter must come after find_package(Python3) (refs #1979) #1981 moves the `add_python_test()` rows into `cmake/tests-python-tools.cmake`, included from `CMakeLists.txt`. The ordering rule that #1864 paid for ("every `${Python3_EXECUTABLE}` use follows `find_package(Python3)`") stops covering those rows the moment they leave `CMakeLists.txt`, because this check globs `CMakeLists.txt` and nothing else. Recursing into `cmake/*.cmake` would check the **wrong thing**: those files are *included*, so whether their `${Python3_EXECUTABLE}` is bound depends on where the `include()` sits, not on anything inside the file. The rule for them is the include's position: > every `include(<path>.cmake)` in a `CMakeLists.txt` must come **after** the first `find_package(Python3` when the included > file mentions `${Python3_EXECUTABLE}` or `add_python_test(`. `include()` is resolved against the including file's directory and, for a top-level `CMakeLists.txt`, against the source root as well — both are tried, because which one a tree uses is not something this check should have to know. Self-test: a synthetic root with a registration `.cmake` included from two `CMakeLists.txt`, one before and one after the `find_package`; the check must return 1 (the early include is a counter-example) and name it. Verified on this tree: still rc 0 with no includes that bind the interpreter, `self-test ok`. Refs #1979, #1981, #1864, #1891. * python_test_order_check: one text for every offset in the include rule (review of #2012) include_offenders() took the find_package's offset from the raw text and each include's from the comment-stripped text, so a comment above a correct find_package made the include after it an offender, reported at the wrong line. Strip first, and take the line from the include's own argument. The self-test checks each tree by itself and no longer leaves a directory behind. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> * python_test_order_check: an include with trailing keywords is still an include include(<file> OPTIONAL), NO_POLICY_SCOPE and RESULT_VARIABLE <v> are legal CMake; the old pattern required the file to be the only argument, so such an include was skipped and the gate reported clean (review of #2123 at 3be7aed). Take the first word as the file; three self-test arms pin it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: t <t@e.com> Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
find_package(Python3 COMPONENTS Interpreter)sat at:761while nine registrations above it expanded${Python3_EXECUTABLE}to the empty string, soCTestTestfile.cmakeheld e.g.— the script as the program. Those tests ran only through their exec bit, which is why a mode-only rewrite of
tools/ci/hrx_build_scope.py(100755 → 100644, no content change) turned the row intoBAD_COMMAND(run 42774598946).The interpreter is now found once, above the first user, and registrations go through one place:
Nine registrations used the variable and now use the helper; the second
find_packageis gone (the later block is still guarded byPython3_Interpreter_FOUND).Why a helper rather than just moving the find_package: moving it fixes today's nine and leaves the next
add_testabove it free to repeat the bug, and it does nothing for a machine without Python3 — where an empty interpreter makes ctest hand a.pyto the kernel as a program.The rule is now checked, on the file, with no configure
tests/python_test_order_check.py(--check --root,--self-test) requires every${Python3_EXECUTABLE}use to follow the firstfind_package(Python3in eachCMakeLists.txt, registered twice like the other*_check.pypairs. Againstorigin/mainit reports exactly the nine the issue counted:and against this head, none. The fix's own comment names the variable, so the checker strips comments first — with a self-test case for that, because a checker that trips on the prose explaining it gets switched off in a week.
Verified
Fixes #1864.