Repository navigation
Stop rewriting overridden configuration files on every configure - #5576
Conversation
Coverage report — base
|
| Module | Line | Δ | Function | Δ | Branch | Δ |
|---|---|---|---|---|---|---|
Svc/OsTime |
70.00 | -26.00 | 83.33 | -16.67 | 60.98 | -31.70 |
Fw/Buffer |
80.41 | -4.92 | 89.47 | -1.83 | 58.62 | -4.54 |
Modules changed
| Module | Line | Δ | Function | Δ | Branch | Δ |
|---|---|---|---|---|---|---|
Svc/OsTime |
70.00 | -26.00 | 83.33 | -16.67 | 60.98 | -31.70 |
Fw/Buffer |
80.41 | -4.92 | 89.47 | -1.83 | 58.62 | -4.54 |
Svc/Ccsds/CfdpManager |
88.86 | -0.10 | 96.00 | +0.00 | 73.36 | -0.27 |
Svc/BufferAccumulator |
88.16 | -0.08 | 94.12 | +0.00 | 74.75 | +0.00 |
Svc/ComAggregator |
95.65 | -0.06 | 93.75 | +0.00 | 96.00 | +0.00 |
Svc/TlmPacketizer |
92.26 | -0.05 | 100.00 | +0.00 | 78.00 | -0.36 |
Svc/PrmDb |
92.75 | -0.03 | 89.47 | -1.44 | 87.98 | +0.42 |
Svc/ComQueue |
97.95 | -0.01 | 100.00 | +0.00 | 88.89 | +0.00 |
Os/Generic |
89.15 | +0.03 | 88.16 | +0.00 | 73.49 | +0.22 |
Svc/Ccsds/CfdpManager/Types |
79.88 | +0.07 | 96.69 | +0.00 | 60.64 | +0.05 |
Svc/FileDownlink |
83.94 | +0.09 | 91.49 | +0.00 | 71.60 | +0.00 |
Drv/Ip |
45.56 | +0.17 | 57.38 | +0.00 | 27.17 | -0.13 |
Os |
21.08 | +0.17 | 23.26 | +0.04 | 18.81 | +0.12 |
Fw/DataStructures |
98.04 | +0.26 | 97.14 | +0.00 | 82.60 | +0.37 |
Os/Generic/Types |
92.14 | +0.35 | 92.86 | +0.00 | 74.77 | +0.00 |
Os/Posix |
64.56 | +0.40 | 85.26 | +0.00 | 45.71 | +0.23 |
Svc/TlmChan |
80.45 | +0.78 | 85.71 | +0.00 | 67.77 | -0.23 |
Svc/FileWorker |
90.57 | +0.85 | 100.00 | +0.00 | 86.06 | +0.68 |
Svc/DpCatalog |
76.22 | +2.05 | 96.97 | +0.00 | 64.98 | +2.03 |
Svc/EventManager |
93.06 | +2.65 | 88.89 | +0.00 | 90.11 | +0.86 |
Svc/Ccsds/SpacePacketFramer |
97.37 | +11.01 | 100.00 | +0.00 | 88.00 | +18.77 |
Svc/FprimeFramer |
100.00 | +13.51 | 100.00 | +0.00 | 95.45 | +23.23 |
Modules without UTs
CFDP/Checksum/GTest, Drv/LinuxGpioDriver, Drv/LinuxI2cDriver, Drv/LinuxSpiDriver, Drv/LinuxUartDriver, Drv/Ports/DataTypes, FppTestProject/FppTest/topology/async, FppTestProject/FppTest/topology/components/Comp, FppTestProject/FppTest/topology/components/Framework, FppTestProject/FppTest/topology/components/Receiver, FppTestProject/FppTest/topology/components/Sender, FppTestProject/FppTest/topology/guarded, FppTestProject/FppTest/topology/sync, FppTestProject/FppTest/topology/top_ports, FppTestProject/FppTest/topology/types, Fw/Com, Fw/Comp, Fw/FilePacket/GTest, Fw/Fpy, Fw/Obj, Fw/Port, Fw/Prm, Fw/Sm, Fw/Test, Fw/Types/GTest, Os/Models, Svc/Ccsds/Types, Svc/FatalHandler, Svc/Subtopologies/CdhCore, Svc/Subtopologies/ComCcsds, Svc/Subtopologies/ComCcsdsSdls, Svc/Subtopologies/ComFprime, Svc/Subtopologies/ComLoggerTee, Svc/Subtopologies/DataProducts, Svc/Subtopologies/DpCompression, Svc/Subtopologies/FileHandling, Svc/Subtopologies/FileHandlingCfdp, Svc/Subtopologies/FileHandlingCfdp/FileHandlingCfdpConfig, TestDeploymentsProject/Ref/DpDemo, TestDeploymentsProject/Ref/PingReceiver, TestDeploymentsProject/Ref/RecvBuffApp, TestDeploymentsProject/Ref/SendBuffApp, TestDeploymentsProject/Ref/Top, TestDeploymentsProject/Ref/TypeDemo, cmake/test/data/TestConfigDeployment, cmake/test/data/TestDeployment/TestBuildAutocoder, cmake/test/data/TestDeployment/TestHeaderAutocoder, cmake/test/data/TestDeployment/TestLinkDepends, cmake/test/data/TestDeployment/TestRelative, cmake/test/data/test-fprime-library/TestLibrary/TestComponent, cmake/test/data/test-fprime-library2/TestLibrary2/TestComponent
|
@kadircanyildirm-crypto would you address the merge conflict? |
| cmake.assert_process_success(CONFIG_BUILD, targets=["TestLibraryNewConfig"]) | ||
|
|
||
|
|
||
| def test_override_survives_reconfigure(CONFIG_BUILD): |
There was a problem hiding this comment.
[Test Quality] must fix Override-removal path has no automated regression test.
The PR description identifies removing an override as exactly the case where the simpler fixes break (a removed override left stale in the build cache), yet it was only verified manually. A future regression there would silently ship stale configuration content. Add a test that drops DpCfg.hpp from the project's CONFIGURATION_OVERRIDES, re-configures, and asserts the destination reverts to F Prime's original file byte for byte.
cc @LeStarch @thomas-bc — low-confidence finding, please confirm.
| list(FIND RECORDED_DESTINATIONS "${DESTINATION}" DESTINATION_INDEX) | ||
| if (DESTINATION_INDEX EQUAL -1) | ||
| list(APPEND RECORDED_DESTINATIONS "${DESTINATION}") | ||
| list(APPEND RECORDED_SOURCES "${SOURCE}") | ||
| else() | ||
| list(REMOVE_AT RECORDED_SOURCES ${DESTINATION_INDEX}) | ||
| list(INSERT RECORDED_SOURCES ${DESTINATION_INDEX} "${SOURCE}") | ||
| endif() | ||
| set_property(GLOBAL PROPERTY FPRIME_CONFIG_DESTINATIONS "${RECORDED_DESTINATIONS}") | ||
| set_property(GLOBAL PROPERTY FPRIME_CONFIG_SOURCES "${RECORDED_SOURCES}") |
There was a problem hiding this comment.
[Maintainability] could fix maint-structural-hazard: two parallel global lists whose index alignment is the invariant.
FPRIME_CONFIG_DESTINATIONS/FPRIME_CONFIG_SOURCES only work while every edit touches both at the same index; a future change that appends/removes one without the other silently maps destinations to the wrong sources. A keyed property per destination (e.g. set_property(GLOBAL PROPERTY "FPRIME_CONFIG_SOURCE_${DESTINATION}" "${SOURCE}") plus a single destinations list) removes the FIND/REMOVE_AT/INSERT dance and the alignment invariant entirely.
| # Copying here rather than at the flush would write an overridden destination twice per CMake run, | ||
| # once with the original and once with the override. Neither write can be skipped by | ||
| # `ONLY_IF_DIFFERENT` because the file holds the other one's content, so the destination's timestamp | ||
| # moves on every configure and everything including it is rebuilt. |
There was a problem hiding this comment.
[Maintainability] could fix maint-misleading-comment: multi-paragraph counterfactual rationale in the function header.
This paragraph describes what the code would do under a rejected design, not what it does; it will silently go stale if the flush strategy changes, and the same rationale already lives in the PR/commit message. One line ("copies are deferred so an overridden destination is written once, by the winning source") carries the needed context.
| list(GET RECORDED_SOURCES ${DESTINATION_INDEX} SOURCE) | ||
| file(COPY_FILE "${SOURCE}" "${DESTINATION}" ONLY_IF_DIFFERENT) | ||
| endforeach() | ||
| endfunction() |
There was a problem hiding this comment.
(Anchored above the offending lines; the diff does not include lines 180-192.)
[Maintainability] future work maint-misleading-comment: header of fprime_internal_get_configuration_destination documents a CONFIG_NAME argument the function does not take (actual: MODULE_NAME, NEW_CONFIG_NAME) and omits the DESTINATION_MODULE/DESTINATION_OVERRIDE/DESTINATION_DIRECTORY values it sets in the caller. Preexisting, but this PR's new code now depends on those undocumented outputs, so the next editor will read a wrong contract.
| cmake.assert_process_success(CONFIG_BUILD, targets=["TestLibraryNewConfig"]) | ||
|
|
||
|
|
||
| def test_override_survives_reconfigure(CONFIG_BUILD): |
There was a problem hiding this comment.
(Anchored on the new test; the diff does not include line 2.)
[Maintainability] future work maint-misleading-comment: the module header still reads test_basic.py: Basic CMake tests but the file is test_config.py holding configuration tests. Preexisting; a reader grepping for the basic tests lands here.
| get_property(FLUSH_SCHEDULED GLOBAL PROPERTY FPRIME_CONFIG_FLUSH_SCHEDULED) | ||
| if (NOT FLUSH_SCHEDULED) | ||
| set_property(GLOBAL PROPERTY FPRIME_CONFIG_FLUSH_SCHEDULED TRUE) | ||
| cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" |
There was a problem hiding this comment.
[Design] could fix Deferred flush leaves the destination holding the losing/old content for the rest of the configure pass.
On a fresh configure (or when a source changes), the destination keeps the original/previous content until the end-of-top-level flush. This is safe only while nothing reads configuration content at configure time; I traced the info-cache sub-build (its fpp targets run at build phase, after its own flush) and the include-path check (paths only), and found no configure-time content reader — but I could not verify this exhaustively. Consider stating this invariant in the function comment so a future configure-time reader does not silently see the losing content.
cc @LeStarch @thomas-bc — low-confidence finding, please confirm.
| get_property(FLUSH_SCHEDULED GLOBAL PROPERTY FPRIME_CONFIG_FLUSH_SCHEDULED) | ||
| if (NOT FLUSH_SCHEDULED) | ||
| set_property(GLOBAL PROPERTY FPRIME_CONFIG_FLUSH_SCHEDULED TRUE) | ||
| cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" |
There was a problem hiding this comment.
[Design] future work Declared CMake minimum (3.18) does not cover this file's real floor.
cmake_language(DEFER) needs 3.19, and this file already used file(COPY_FILE) (3.21) and cmake_path() (3.20) before this PR, so the effective floor was already above the declared cmake_minimum_required(VERSION 3.18). Preexisting, not widened here; the PR description already proposes a separate issue — recording so it is tracked.
| foreach(DESTINATION_INDEX RANGE ${LAST_INDEX}) | ||
| list(GET RECORDED_DESTINATIONS ${DESTINATION_INDEX} DESTINATION) | ||
| list(GET RECORDED_SOURCES ${DESTINATION_INDEX} SOURCE) | ||
| file(COPY_FILE "${SOURCE}" "${DESTINATION}" ONLY_IF_DIFFERENT) |
There was a problem hiding this comment.
[Documentation] could fix Build-system docs do not describe the new deferred single-write behavior for overridden configuration files.
docs/user-manual/framework/configuring-fprime.md and docs/user-manual/build-system/ describe CONFIGURATION_OVERRIDES but never say when overridden destinations are materialized; this PR changes that to a single deferred write at the end of the top-level configure, and unchanged destinations are no longer rewritten. A short note in configuring-fprime.md would explain why no-op re-configures no longer rebuild dependents. The generated CMake API reference picks up the new #### doc blocks automatically, so no action is needed there.
cc @LeStarch @thomas-bc — low-confidence finding, please confirm.
| get_property(FLUSH_SCHEDULED GLOBAL PROPERTY FPRIME_CONFIG_FLUSH_SCHEDULED) | ||
| if (NOT FLUSH_SCHEDULED) | ||
| set_property(GLOBAL PROPERTY FPRIME_CONFIG_FLUSH_SCHEDULED TRUE) | ||
| cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" |
There was a problem hiding this comment.
[Documentation] future work Build-system SDD (cmake/docs/sdd.md) does not cover the configuration-assembly/override mechanism this PR redesigns.
The SDD predates config_assembler.cmake and never described override handling, so the gap is preexisting, not introduced here. The new deferred-flush design (global-property source map plus cmake_language(DEFER) at the top-level directory) is now load-bearing build-system behavior worth capturing when the SDD is next updated.
| set_property(GLOBAL PROPERTY FPRIME_CONFIG_SOURCES "${RECORDED_SOURCES}") | ||
|
|
||
| if (NOT EXISTS "${DESTINATION}") | ||
| file(COPY_FILE "${SOURCE}" "${DESTINATION}") |
There was a problem hiding this comment.
[Correctness] could fix correctness-other: overridden destination holds the original (or previous configure's) content until the deferred flush runs.
Trigger: any configure-time logic that reads an overridden destination's bytes between module registration and the end of top-level directory processing (e.g. a project CMakeLists.txt doing file(READ)/configure_file on a config header). Before this PR the override content landed at registration time; now it lands only at the flush. No in-repo configure-time reader of these files exists today, so the consequence is only reachable from project code outside this repository.
cc @LeStarch @thomas-bc — low-confidence finding, please confirm.
| get_property(FLUSH_SCHEDULED GLOBAL PROPERTY FPRIME_CONFIG_FLUSH_SCHEDULED) | ||
| if (NOT FLUSH_SCHEDULED) | ||
| set_property(GLOBAL PROPERTY FPRIME_CONFIG_FLUSH_SCHEDULED TRUE) | ||
| cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" |
There was a problem hiding this comment.
[Correctness] future work correctness-other: cmake_language(DEFER) requires CMake 3.19, but the top-level cmake_minimum_required still declares 3.18.
Trigger: configuring with CMake 3.18 (the declared minimum). Consequence: configure aborts with an unknown-subcommand error instead of a clear version message. The effective floor was already 3.21 before this PR (file(COPY_FILE), cmake_path() in this same file), so this PR does not widen it — flagged as preexisting per the PR's own Future Work note; the declared minimum should be raised in a follow-up.
Covers nasa#4676. An overridden configuration destination has two writers per CMake run: the module supplying the original file and the module supplying the override. Each writes the destination while it holds the other one's content, so ONLY_IF_DIFFERENT can never skip either copy and the destination's timestamp moves on every configure, rebuilding everything that includes it. Both writers now record the source that should end up at the destination, and a single deferred pass at the end of the top-level directory writes each destination once, after every override is known. A destination that already holds the winning content is left untouched. Removing an override still restores the original, because the recorded map is rebuilt from scratch on each configure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lestarch-autobot
left a comment
There was a problem hiding this comment.
Automated review summary (run 1)
Per-agent results
| Agent | must fix | suggestion | could fix | future work | outstanding | Verdict |
|---|---|---|---|---|---|---|
| Security Vulnerabilities | 0 | 0 | 0 | 0 | 0 | Go |
| Supply Chain / Runner Safety | 0 | 0 | 0 | 0 | 0 | Go |
| F Prime C/C++ Design | 0 | 0 | 0 | 0 | 0 | Go |
| Documentation Currency | 0 | 0 | 1 | 1 | 2 | Go |
| Design | 0 | 0 | 1 | 1 | 2 | Go |
| Architecture | 0 | 0 | 0 | 0 | 0 | Go |
| Test Quality | 1 | 1 | 0 | 0 | 2 | No-Go |
| Correctness | 0 | 0 | 1 | 1 | 2 | Go |
| Maintainability | 0 | 1 | 2 | 2 | 5 | Go |
| CI safety | — | — | — | — | — | Go |
| Totals | 1 | 2 | 5 | 5 | 13 | No-Go |
Supply-chain surfaces
| Surface | Outstanding |
|---|---|
| Dependencies | clean |
| Vendored / submodule | clean |
| Build / test infrastructure | clean |
| Workflows / actions / scripts | clean |
| Generator output | clean |
| Prompt-injection | clean |
| Review-system integrity | clean |
Outstanding must-fix items (1)
Test Quality
- Override-removal path has no automated regression test — #5576 (comment)
Merge readiness
Merge readiness: No-Go — Test Quality has 1 outstanding must-fix item (missing override-removal regression test).
Agents that did not run on this PR
- None — all registered reviewers ran.
All stations report in; one item on the checklist before this build is go for launch.
c506ffa to
91e0ed0
Compare
Co-authored-by: lestarch-autobot <lestarch+autobot@gmail.com>
|
Rebased onto |
|
@kadircanyildirm-crypto would you format python and consider the missing test? |
Black also reformats the assertion added by the review suggestion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
All four red checks are external-repo jobs, and none of them traces back to this change.
The branch is 24 commits behind AI use per |
Change Description
An overridden configuration destination is written twice per CMake run: once by the module that supplies the original file, once by the module that supplies the override. Each of them writes while the file holds the other one's content, so
ONLY_IF_DIFFERENTcan never skip either copy. The destination's timestamp moves on every configure, and everything including it is rebuilt.Both paths now record which source should end up at the destination instead of copying straight away. A single pass, deferred to the end of the top-level directory, writes each destination once, at a point where every override is known. A destination that already holds the winning content is left alone.
Rationale
The write cannot simply be skipped by whoever loses. When the original is processed its overrides have not been registered yet, and they cannot be: an override is rejected outright if the file it overrides has not been seen, so the module supplying the original is always processed first. Any decision made at that moment relies on the previous configure, which cannot tell "the override is still there" apart from "the override was removed". Each variant I worked through along those lines would leave a removed override stale in the build cache, which is the same class of problem as the one being fixed. Deferring the write until all modules are registered is what actually removes the ambiguity.
Worth noting because I got it wrong at first: this is two writers per CMake run, not per build. The
info-cachesub-build is a separate CMake process that also materialises configuration into the main build cache, and it runs before the main configure does its own pass. So an overridden destination is written four times per top-level configure, not two. Both processes run this same code, so the single fix covers both.Testing/Review Recommendations
On
devel@a24a4c8,TestConfigDeployment, which declares four overrides:info-cachesub-build by handTestDeployment, which declares no overrides, moves nothing either way — the four that move map one-to-one onto the four declared overrides.test_override_survives_reconfigureis the regression test. It fails ondevelwith only the test applied, which is the run I would check first — #3933 fixed a nearby build-system bug without one, and #3929 had to be opened to add it afterwards.test_config.pywith this changetest_config.pywith the fix reverted, test keptpytest -sfromcmake/test, the command the CI job runsRemoving an override is worth reviewing specifically, since that is where the simpler fixes break. Dropping
DpCfg.hppfrom the project'sCONFIGURATION_OVERRIDESand re-configuring restores F Prime's original file byte for byte, because the recorded map is rebuilt from scratch on every configure.Formatting checked with
black==21.6b0, the versionpython-format.ymlpins.Future Work
cmake_minimum_requiredin the top-levelCMakeLists.txtsays 3.18, butconfig_assembler.cmakealready usedfile(COPY_FILE)(3.21) andcmake_path()(3.20) before this change, so the real floor was already higher than the declared one.cmake_language(DEFER)used here needs 3.19 and is therefore covered. The stale declaration seems worth its own issue rather than a change smuggled in here.AI Usage (see policy)
I used Claude Code. It helped me trace where the duplicate write comes from, wrote this change and the test, and drafted this description. The work is limited to
cmake/config_assembler.cmakeand the configuration test module.The output was heavily modified rather than used as-is. The first designs had the losing writer skip its copy; working through them showed each would leave a removed override stale in the build cache, so they were dropped in favour of deferring the write. Every number above comes from a run I did myself.