Repository navigation
fix(build): build the backend registry, and stop system.linux eating rtos - #118
Conversation
|
Merge-order note: this and #115 are siblings off #114, and both insert a block into |
07594ab to
82a5b5b
Compare
|
Rebased onto #116 → #114. #113 is closed and this branch carried its two commits; #116 repairs the same two things on master, so they are dropped here. Stack is now #116 → #114 → this. 39/39 locally, and the default non-test build still builds. The |
82a5b5b to
3c72594
Compare
|
Rebased onto current master (#116 and #114 have merged and were dropped as already-upstream), then onto #125 — master is red on the registration guard for Also folds in That last line is the parser fix visible end to end — All 11 backends register (cmake, ninja, make, kbuild, zephyr, freertos, nuttx, meson, autotools, buildroot, cargo); none carries a TODO or stub marker. |
16df1c1 to
e724633
Compare
srpatcha
left a comment
There was a problem hiding this comment.
Review — eos#118 "fix(build): build the backend registry, and stop system.linux eating rtos"
head: e724633 author: Kartikey1306 ci: pass
Verdict: Correct diagnosis of a real three-link chain (unbuilt backends/, shadowed
eos/backend.h, system.linux swallowing the sibling rtos:), and the parser fix is
right for every config in examples/. Two of the three build-side fixes are applied at
the wrong level — a global include root and per-consumer link lines instead of the
declared dependency on eos_systems — and the parser fix is guarded by a hand-maintained
key list that will silently drift.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | Medium | systems/CMakeLists.txt:9 |
systems/src/firmware.c:6 includes eos/backend.h and calls eos_backend_find(), but eos_systems still links only eos_core eos_toolchains. The PR fixes the link failure at the consumers instead — tests/CMakeLists.txt:141 adds eos_backends to test_firmware, and cmd/eos/CMakeLists.txt:5 already listed it. The library that owns the dependency still does not declare it, so every future consumer of eos_systems has to rediscover this. |
target_link_libraries(eos_systems PRIVATE eos_core eos_toolchains eos_backends). Then eos_backends can come off the test_firmware line — it arrives transitively with the PUBLIC include dir backends/CMakeLists.txt:17 already declares. |
| 2 | Medium | CMakeLists.txt:62-66 |
backends/include is added to the global include_directories(), so a build-orchestration header is now on the include path of every target in the tree, including eos_kernel (CMakeLists.txt:108), eos_hal (:92) and eos_drivers (:122). Nothing stops kernel or HAL code from #include "eos/backend.h" and picking up a dependency that master design §5.1 forbids. The global entry is also not needed: backends/CMakeLists.txt:17 already exports that directory PUBLIC. |
Do not delete include/eos/backend.h — replace its contents with backends/include/eos/backend.h, delete backends/include/, and drop both the new global path entry and the target_include_directories in backends/. include/ stays the single public-header contract (.ai/architect.md, "Target shape"), the shadowing that caused finding 2 in the PR body becomes structurally impossible, and no new header root exists to point up a tier. |
| 3 | Medium | core/src/config.c:186-199 |
The pop-back fires only for seven literal keys (kind, linux, kernel, rootfs, rtos, image_format, output). case SEC_SYSTEM: at core/src/config.c:209-222 is the real authority on what a system child key is, and the two lists are kept in sync by hand with nothing checking. Add an eighth key to SEC_SYSTEM later and this exact swallow bug returns for it, silently, with no test that fails. |
The key list is not load-bearing: in this format every descendant of a system sub-section sits at indent ≥ 2 (system.linux.provider at 2, rtos-entry keys at 3), and list items continue at config.c:170 before reaching this block. Drop the strcmp chain and keep the guard structural: if (indent <= 1 && (section == SEC_SYSTEM_LINUX || ... || section == SEC_SYSTEM_RTOS_ENTRY)) section = SEC_SYSTEM;. Same behaviour on every config in examples/, and it cannot drift. |
| 4 | Low | core/src/config.c:202-208 |
The same defect is left in place one section up, in the function this PR is fixing. SEC_TOOLCHAIN_LINUX pops back for exactly one sibling (rtos, :204) and SEC_TOOLCHAIN_RTOS pops back for nothing, so a target: at toolchain's own indent written after the linux:/rtos: blocks lands in cfg->toolchain.rtos_target (:207) instead of cfg->toolchain.target — silently, exactly like rtos_count staying 0. No file under examples/ orders a config that way, which is why this is Low and not the same severity as the system case. |
Apply the structural guard from finding 3 to SEC_TOOLCHAIN_LINUX/SEC_TOOLCHAIN_RTOS in the same commit; it is three lines and removes the class rather than one instance. |
| 5 | Low | core/src/config.c:209-222, core/include/eos/config.h:75-85 |
Adjacent silent drop the PR does not claim to fix, noted because it is the same failure mode: system.entry is parsed by nothing — case SEC_SYSTEM: has no entry branch and EosSystemConfig has no entry field — yet six shipped configs set it (examples/ui-touchscreen/eos.yaml:19, posix-app, uart-echo, multitask-rtos, multicore-amp, blink-gpio). A non-hybrid system.entry goes nowhere and nothing reports it. |
Out of scope for this PR. Worth its own issue: either give EosSystemConfig an entry field or make an unrecognised key under a known section a diagnostic rather than a no-op. The second is the fix that would have caught all of these. |
| 6 | Low | pr.json body |
The body describes a 5-file change verified at ctest 39/39. The head under review also builds cmd/eos and the 2026-09-02 comment reports 40/40 plus the eos binary. The body is the first thing a reviewer reads and it no longer matches the head. |
Fold the 2026-09-02 comment into the body before merge. |
The claims are backed: 26 required checks pass in checks.txt, including Cross-compile ARM Cortex-M4, Cross-compile ARM64 kernel, Default configuration (no tests, no product) and QEMU Boot Test, which is what the NOT CMAKE_CROSSCOMPILING guard and
the new add_subdirectory calls needed to be exercised by. The registration-guard entry
removed from tests/unit/test_cmake_test_registration.py:32-35 is correct — test_firmware.c
is registered at tests/CMakeLists.txt:140-142, so leaving it in NOT_BUILT would have
failed test_not_built_list_has_no_stale_entries. No check is weakened, no assertion
removed, no permission widened.
Architecture conformance
§5.1 architectural law — conforms in effect, weakened structurally. No new
dependency points up a tier in this diff. But finding 2 removes the barrier: making
backends/include a global header root means every lower-layer target in the repo can
now resolve eos/backend.h. core/src/scheduler.c:6 already includes it, and
eos_core neither links eos_backends nor adds its include dir — it compiles only
because of the new global path, while eos_backends links eos_core (backends/CMakeLists.txt:20).
That is a header-level cycle held together by a global include path. Finding 2's fix
resolves it into the shared contract in include/, which is what include/ is for.
§21 tier placement — deviates, and the deviation is older than this PR. backends/
wraps CMake, Ninja, Make, Kbuild, Zephyr, FreeRTOS, NuttX, Meson, Autotools, Buildroot
and Cargo, and cmd/eos/main.c drives them from a CLI. That is build orchestration.
Master design §9 makes eBuild "the central developer control plane", and Appendix C
lists "Approve eBuild as the only build orchestration/control plane" as a leadership
decision. §21 puts eos and ebuild in the same Tier 1 without drawing a boundary
between them. This PR did not create that overlap, but it is the change that makes it
real — before it, backends/ compiled into nothing and the eos binary did not exist;
after it, the repo ships a second build-orchestration control plane. Flagging for an
architecture decision, not asking for a revert: the dead code was worse. Proposal
appended to .ai/autoreview/proposals/2026-09.md.
§21.1 split policy — not engaged; nothing is being split out here.
Proposed changes
Smallest sequence that keeps every target building:
git mv backends/include/eos/backend.h include/eos/backend.h(overwriting the stale
copy rather than deleting it), removetarget_include_directories(eos_backends ...)
frombackends/CMakeLists.txt:16-18, and drop thebackends/includeline from
CMakeLists.txt:65. Build stays green at every step: the header content is
unchanged, only its location.- Add
eos_backendstotarget_link_librariesinsystems/CMakeLists.txt:9; remove
it fromtests/CMakeLists.txt:141. - In
core/src/config.c, replace the seven-keystrcmpchain at:193-198with the
section test alone, and extend the same guard toSEC_TOOLCHAIN_LINUX/
SEC_TOOLCHAIN_RTOS. - Add one
test_firmwarecase for the toolchain ordering in finding 4 so the
generalised guard is covered, not just thesystempath.
Steps 1 and 2 are pure build-graph moves with no behaviour change; step 3 changes no
outcome for any config in examples/; step 4 is new coverage. None of them needs to be
in the same commit as another.
Not checked
- I did not build this branch or run
ctest. The 40/40 andeos infooutput in the
2026-09-02 comment are the author's, not reproduced here; the CI results in
checks.txtare the independent evidence and they are green for the head SHA. - Finding 4 is reasoned from
core/src/config.c:202-208, not from a failing run — I
read everyeos.yamlunderexamples/and none orderstoolchain:in the way that
triggers it, so I could not demonstrate it. indent_level()(core/src/config.c:36-39) counts spaces only; a tab-indented config
yields indent 0 for every line and behaves unpredictably in this block and in the
pre-existing one at:234. Pre-existing, not introduced here, and I did not
characterise it.- Whether
eos_backend_register()'sEOS_MAX_BACKENDS 16bound is enforced for the 11
registrationscmd/eos/main.cperforms —backends/src/backend.cis outside this
diff and I did not read it.
Automated architecture review of e7246335fae7 — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
…ARM file Answers the review on embeddedos-org#126. Finding 1 (Medium) -- the head carried an undisclosed second change, the test_crypto_ed25519_loworder registration, which contradicted this PR's own 2026-09-02 comment ("this PR changes one file ... and does not touch tests/"). It was also the fourth open copy of that registration, at a different position from embeddedos-org#127's. Dropped: the branch is rebased to the toolchain change alone, and embeddedos-org#127 is the one to take for the registration -- it restores the four lines to their original position, keeps the crypto suites grouped, and is the position embeddedos-org#122 is already written against. Finding 2 (Medium) -- nothing would catch this coming back. Every ARM toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no cross build links an executable, so `Cross-compile ARM Cortex-M4` passed identically with the duplicate present and absent; it passed on the unfixed commit. The defect surfaced by accident when embeddedos-org#118 briefly put cmd/eos into the cross build, and embeddedos-org#118 has since gated that to host builds -- removing the accident too. Adds tests/unit/test_toolchain_specs.py, modelled on test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each -specs=<file>, and fail if one appears in more than one variable that reaches a link line (CMake links through the compiler driver, so C/CXX flags do). A second test asserts specs are declared on the linker flags rather than the compiler flags, which is the arrangement that turns into a duplicate the moment someone adds the linker entry that looks missing. No ARM toolchain needed; runs in the existing pytest job. Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken today, and one added linker -specs= away from the same failure. Moved, so all three ARM bare-metal toolchains state it the same way and the new guard is green across all of them. Verified: pytest tests/unit/test_toolchain_specs.py 2 passed pytest tests/ all pass discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than once on both tests, so the guard catches the original defect rather than restating the fix. No ARM toolchain on this machine, so the cross link itself is NOT verified here; that remains CI's to show. Refs embeddedos-org#126
…ARM file Answers the review on embeddedos-org#126. Finding 1 (Medium) -- the head carried an undisclosed second change, the test_crypto_ed25519_loworder registration, which contradicted this PR's own 2026-09-02 comment ("this PR changes one file ... and does not touch tests/"). It was also the fourth open copy of that registration, at a different position from embeddedos-org#127's. Dropped: the branch is rebased to the toolchain change alone, and embeddedos-org#127 is the one to take for the registration -- it restores the four lines to their original position, keeps the crypto suites grouped, and is the position embeddedos-org#122 is already written against. Finding 2 (Medium) -- nothing would catch this coming back. Every ARM toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no cross build links an executable, so `Cross-compile ARM Cortex-M4` passed identically with the duplicate present and absent; it passed on the unfixed commit. The defect surfaced by accident when embeddedos-org#118 briefly put cmd/eos into the cross build, and embeddedos-org#118 has since gated that to host builds -- removing the accident too. Adds tests/unit/test_toolchain_specs.py, modelled on test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each -specs=<file>, and fail if one appears in more than one variable that reaches a link line (CMake links through the compiler driver, so C/CXX flags do). A second test asserts specs are declared on the linker flags rather than the compiler flags, which is the arrangement that turns into a duplicate the moment someone adds the linker entry that looks missing. No ARM toolchain needed; runs in the existing pytest job. Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken today, and one added linker -specs= away from the same failure. Moved, so all three ARM bare-metal toolchains state it the same way and the new guard is green across all of them. Verified: pytest tests/unit/test_toolchain_specs.py 2 passed pytest tests/ all pass discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than once on both tests, so the guard catches the original defect rather than restating the fix. No ARM toolchain on this machine, so the cross link itself is NOT verified here; that remains CI's to show. Refs embeddedos-org#126
…removed `CI — eos` and `Python Unit Tests` are red on master (769191a), and both fail for one reason: these suites exist in tests/ but no add_executable() in tests/CMakeLists.txt builds them, so they never run: ['test_crypto_ed25519_loworder.c'] 3aa8644 (embeddedos-org#93, "remove duplicate test targets from tests/CMakeLists.txt") removed all four lines that build and register test_crypto_ed25519_loworder. It was not a duplicate — there was exactly one registration before that commit, and it took it. What stopped running is the suite that guards embeddedos-org#101: Ed25519 rejecting low-order public keys. A low-order key makes every term of the verification equation collapse to the identity regardless of the message, so a signature of all zeros verifies against any content at all. The fix is still in services/crypto/src/ed25519_verify.c and still correct; nothing has been checking it since embeddedos-org#93 merged, and nothing would have noticed if a later change had removed it too. Restored verbatim from 3aa8644^, in its original position before test_crypto_sha512. No other file changes. The guard that caught this is test_cmake_test_registration.py, added recently. It did exactly its job — this is the first thing it found. Verified: test_cmake_test_registration.py -> 5 passed (1 failed on master) pytest tests/ -> 15 passed (14 passed 1 failed on master) test_crypto_ed25519_loworder -> 5/5 tests passed ctest -> 39/39 passed (master builds 38)
…-back structural Answers the review on embeddedos-org#118. Finding 1 (Medium) -- systems/src/firmware.c includes eos/backend.h and calls eos_backend_find(), but eos_systems linked only eos_core and eos_toolchains; the dependency was satisfied at each consumer instead, so every future user of eos_systems had to rediscover it. eos_systems now declares it, and eos_backends comes off the test_firmware line. Finding 2 (Medium) -- backends/include was added to the global include_directories(), putting a build-orchestration header on the include path of every target in the tree, eos_kernel and eos_hal included. Nothing then stops kernel or HAL code acquiring a dependency master design 5.1 forbids. Taken the reviewer's version rather than a narrower one: the header moves to include/eos/backend.h -- replacing the stale copy that caused the original shadowing -- backends/include is gone, and both the global entry and backends' own target_include_directories with it. include/ stays the single public-header contract, and the shadow is now structurally impossible rather than merely resolved. Finding 3 (Medium) -- the pop-back fired only for seven literal keys, which made it a second authority on what a `system` child key is, kept in step with `case SEC_SYSTEM:` by hand and by nothing else. Add an eighth key there and the swallow returns for it silently. Now structural: every descendant of a system sub-section sits at indent >= 2 and list items `continue` earlier, so indent <= 1 is exactly the condition. Same behaviour on every config under examples/, and it cannot drift. Finding 4 (Low) -- applied the same structural guard to SEC_TOOLCHAIN_LINUX/SEC_TOOLCHAIN_RTOS, which popped back for one sibling and none respectively. A `target:` at toolchain's own indent written after the linux:/rtos: blocks landed in toolchain.rtos_target instead of toolchain.target. Not reachable by any config in examples/ today, which is why it had not bitten. Also dropped the test_crypto_ed25519_loworder registration hunk; this branch is stacked on embeddedos-org#127, which is the copy to keep. NOT fixed, and confirmed while testing: finding 5, `system.entry` is parsed by nothing -- no branch in `case SEC_SYSTEM:` and no field on EosSystemConfig -- while six shipped configs set it. examples/multicore-amp/eos.yaml sets `entry: main.c` and `eos info` reports nothing for it. Same failure mode, but it needs a new struct field and a decision about what consumes it, so it is a follow-up rather than something to fold in here. Verified: ctest 40/40 PASS pytest tests/ 15 passed eos info, in examples/industrial-gateway: kind: hybrid / kernel provider: buildroot / RTOS targets (1) which is the parser fix visible end to end -- RTOS targets reads 0 before it. examples/multicore-amp reports 0, correctly: that config has no rtos list at all. Refs embeddedos-org#118
e724633 to
13c20b4
Compare
srpatcha
left a comment
There was a problem hiding this comment.
Review — eos#118 "fix(build): build the backend registry, and stop system.linux eating rtos"
head: 13c20b4 author: Kartikey1306 ci: fail (both failures inherited from eBoot master — see CI)
Verdict: The build wiring is right and I reproduced every figure in the PR (40/40 ctest, 15/15 pytest, clean default build). The parser fix is real but only for 2-space YAML: both new pop-back guards hardcode indent <= 1 against an indent_level() that divides by two, so a 4-space config still gets RTOS targets (0) — plus a new symptom, the RTOS toolchain target silently overwriting the Linux one.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | High | core/src/config.c:161-164, :175-178 |
Both new guards test indent <= 1, but indent_level() is spaces / 2 (:36-40), so "system's own child level" is a 2-space assumption, not the structural fact the comment claims. Every other part of this parser is indentation-agnostic — if (indent == 0) for top level, and the descend-on-key cases carry no indent test — so 4-space YAML works everywhere except here. With 4 spaces the guards never fire and the exact defect this PR is titled for comes back, silently, together with a second one. Verified with the eos binary this PR builds, on two files with identical semantics: 2-space → Toolchain: target aarch64-linux-gnu / rtos arm-none-eabi, RTOS targets (1), Firmware Targets (1). 4-space → Toolchain: target arm-none-eabi (the RTOS target overwrote the Linux cross target), RTOS targets (0), Firmware Targets (0). That is "a hybrid build produced no RTOS firmware without saying so", still. |
Record the indent at which each sub-section was entered and pop when a later key sits at or below it, instead of comparing to a literal. At each section = SEC_SYSTEM_LINUX; continue;-style descent, also store section_indent = indent;, then the guard becomes if (indent <= section_indent && …). That is indentation-agnostic, removes the /2 dependency from the pop-back logic, and also repairs the pre-existing literal at :271 (SEC_SYSTEM_KERNEL, indent <= 1 && key == "rootfs"), which has the same flaw. Add a 4-space fixture to test_firmware.c::test_config_load_hybrid — today it only loads examples/industrial-gateway/eos.yaml, which is 2-space, so nothing in the suite can see this. |
| 2 | Medium | include/eos/backend.h:1-13 |
The surviving public header lost the extern "C" guard the deleted include/ version had (#ifdef __cplusplus / extern "C" {). .ai/architect.md calls include/ "public headers — the contract other repos compile against", and EoStudio and eBrowser are C++ clients of this tree. Verified nothing in-tree breaks: no .cpp/.cc/.hpp/.cxx translation unit includes eos/backend.h. Worth settling rather than drifting — only 1 of the 4 headers in include/eos/ (package.h) carries the guard now, and this change took it from 2 to 1. |
Re-add the __cplusplus guard around the declarations in the surviving header. |
| 3 | Medium | include/eos/backend.h (whole file), pr.json body |
Consolidating two divergent copies of one public header is the right call, but it changes the declared API and the body does not mention it. EosBackend::name goes const char * → char[EOS_MAX_NAME]; the eos_backend_configure_fn / _build_fn / _install_fn typedefs and void *user_data are removed; returns move int → EosResult; and eos_backend_init_all() is deleted from the public surface. Verified there is no in-tree fallout — zero references to eos_backend_init_all anywhere in the tree, and EosBackend::user_data had no users (the only user_data hits are EosNode's, in core/src/graph.c:32). But §23.2 makes the source-level EoS API a versioned contract and §30/§38 want an RFC/ADR for this class of change, and a reader of the diff cannot tell which of the two copies was live. |
One paragraph in the body: backends/include was on eos_backends' PUBLIC include path but backends/ was in no add_subdirectory(), so nothing consumed it — the include/ copy is what systems/src/firmware.c actually compiled against, and this PR replaces its signatures with the backends/ shape. State that, and that no consumer used the removed declarations. That turns an unexplained API break into a deliberate consolidation. |
| 4 | Low | CMakeLists.txt:16-29 |
The if(NOT CMAKE_CROSSCOMPILING) gate is correct on its own merits — a host build-system driver has no business in a cross build — but the justification in the comment is about to become wrong. The comment attributes the gate to nosys.specs being supplied twice on a Cortex-M4 link, and eos#126 fixes exactly that; its own docstring says the defect "was found by accident, when a PR briefly put cmd/eos into the cross build", which is this PR. Once #126 lands, the comment names a bug that no longer exists as the reason for a gate that should stay anyway. |
Keep the gate, and reword the comment to lead with the real reason (cmd/eos is a host tool, gated like services/pkg), with the nosys.specs incident as a historical note pointing at #126 rather than as the rationale. |
| 5 | Low | tests/CMakeLists.txt:93-96 |
The test_crypto_ed25519_loworder registration block is eos#125's fix, carried here so this branch is not red on the registration guard. Confirmed necessary: origin/master has tests/test_crypto_ed25519_loworder.c but does not register it, so tests/unit/test_cmake_test_registration.py fails on master today. But eos#126 adds the identical block at the identical anchor, so #118, #125 and #126 will all conflict in tests/CMakeLists.txt, and whichever two land later each need a one-hunk resolution. This is the same situation the author already flagged for #115. |
Land #125 first, then rebase #118 and #126 and drop the duplicated hunk from both. Worth adding to the existing merge-order comment so the third collision is not discovered at merge time. |
CI
Two checks red, and neither is caused by this PR:
Cross-compile ARM64 kernel—.github/workflows/eos-simulation.yml:28-33checks outembeddedos-org/eBoot@masteralongsideeosand builds it. eBoot'smasterdoes not compile. Job log:eBoot/include/eos_image.h:135and:142,error: 'eos_image_header_t' has no member named 'reserved', failingeboot_core. I reproduced this independently by building eBootorigin/master(22d8f8b) in a clean worktree; it has three distinct pre-existing errors from one bad merge, and eBoot#94 already targets them.Full-stack integration summary—needs: [build-kernel, …](:325-329), so it reports FAIL purely because of the above.QEMU ARM64 simulation (13 TCs)showsskippingfor the same reason.
Everything else is green, including Cross-compile ARM Cortex-M4 (both instances), Host Tests (x86_64), Build & Test (Linux x86_64), Default configuration (no tests, no product), Static Analysis, Analyze (C/C++), CodeQL and QEMU Boot Test.
Reproduced locally at head 13c20b4 (fresh worktree, host gcc):
cmake -S . -B bt -DEOS_BUILD_TESTS=ON && cmake --build bt # rc=0, no errors
ctest --test-dir bt --no-tests=error # 40/40 passed
cmake -S . -B bdef && cmake --build bdef # default (no tests) rc=0, 0 errors
pytest tests/ -q # 15 passed
pytest tests/unit/test_cmake_test_registration.py -q # 5 passed
Every figure in the PR body and comments checks out, including the "was 38 on master" delta and the working eos info output. Finding 1 was found by running that same binary against a 4-space config, not by reading.
Architecture conformance
Conforms on layering; the header consolidation is an improvement; one deviation on process.
- §5.1 /
.ai/architect.mddependency direction — conforms, and thesystems/CMakeLists.txtchange is the right correction.systems/src/firmware.cincludeseos/backend.hand callseos_backend_find(), soeos_systemsis whereeos_backendsbelongs; declaring it at each consumer made every future consumer rediscover it. Nothing points up a tier:eos_backendslinkseos_corePRIVATE,eos_systemslinkseos_core eos_toolchains eos_backends, andcmd/eossits above all of them. No product-repo dependency is introduced. include/as the contract — conforms in direction. Two files namedeos/backend.hwith different struct layouts, one of them shadowing the other via a PUBLIC include path, is precisely the "widen a public header to make one caller compile" failure mode inverted; collapsing to a single copy underinclude/is what.ai/architect.mdasks for. Findings 2 and 3 are about how, not whether.- §21 tier placement — correct.
eosis Tier 1 Foundation; the backend registry, the systems layer and theeosCLI all belong here. §21.1 is not engaged — nothing is being split out. .ai/architect.md, "Restructure and change behaviour in the same commit" — deviates, but defensibly. This PR both wires upbackends/+cmd/eosand changes the config parser. The coupling is genuine:test_firmwareis the test that proves the wiring, and it cannot pass without the parser fix, so neither half lands alone. Worth saying explicitly in the body rather than leaving a reviewer to infer it. The part that is not coupled is the toolchain pop-back (:175-178) — no test needs it, no config exercises it, and the author says so ("No config underexamples/is ordered that way today, which is the only reason it has not bitten"). Finding 1 shows that untested half is where the remaining defect lives.- Weakened checks — checked, none. Removing
test_firmware.cfrom the skip list intests/unit/test_cmake_test_registration.pyis the opposite: a suite that was excused is now built and run.
Proposed changes
- Finding 1 — remembered section indent plus a 4-space fixture. This is the one that matters; without it the PR's headline fix covers only the indentation style the examples happen to use.
- Finding 2 — restore the
extern "C"guard. One hunk. - Findings 3 and 4 — body paragraph and comment reword. No code.
- Finding 5 — land eos#125, then rebase #118 and #126 and drop the duplicated
tests/CMakeLists.txthunk from both. - Independently, and not this author's problem: eBoot#94 has to land before any
eosPR can show a greenCross-compile ARM64 kernel.
I opened no fix PR. Finding 1 is High and the fix is small, but it can only be expressed on top of this branch — the indent <= 1 guards do not exist on master — and the brief forbids touching a branch that belongs to an open PR.
Not checked
- The 4-space behaviour is verified; the proposed fix is not. I did not implement the remembered-indent change, so I have not confirmed it keeps 40/40 green or that
indent <= section_indentis right at every descent site (SEC_PKG_*andSEC_LAYERSreach the guards via different paths and were not analysed). - Tab-indented YAML was not tested.
indent_level()counts only spaces, so a tab-indented file yields indent 0 for every line and would be parsed as all-top-level. YAML forbids tabs for indentation, so I did not treat this as a finding, but nothing in the parser rejects it either. - No cross build was run locally.
Cross-compile ARM Cortex-M4passing in CI is the only evidence that theNOT CMAKE_CROSSCOMPILINGgate does what finding 4 discusses; I did not installarm-none-eabito confirm thenosys.specsfailure mode the comment describes. - The 11 backends were not exercised.
eos_backend_registry_init()plus eacheos_backend_*_init()now compile and the binary runseos info, but no backend'sconfigure/build/install/cleanwas invoked against a real build system. The author's claim that none carries a TODO or stub marker was not verified. eos infowas run against synthetic configs andexamples/industrial-gateway, not against every file underexamples/; other examples may have their own indentation.- The removed public declarations were checked for in-tree callers only. Whether any other repo in the org compiles against
include/eos/backend.hwas not checked, which is the gap finding 3 asks the body to close.
Automated architecture review of 13c20b404eba — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
Residue from 0957ac4, which dropped the borrowed test_crypto_ed25519_loworder registration and left the blank line that had separated it from the block below. It is the only remaining change this PR makes to tests/CMakeLists.txt, it changes nothing, and it sits at the exact anchor where embeddedos-org#118, embeddedos-org#119 and embeddedos-org#127 each add the real registration block -- so it would have conflicted with whichever of those lands first, for no reason at all. tests/CMakeLists.txt is now untouched by this branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ARM file Answers the review on embeddedos-org#126. Finding 1 (Medium) -- the head carried an undisclosed second change, the test_crypto_ed25519_loworder registration, which contradicted this PR's own 2026-09-02 comment ("this PR changes one file ... and does not touch tests/"). It was also the fourth open copy of that registration, at a different position from embeddedos-org#127's. Dropped: the branch is rebased to the toolchain change alone, and embeddedos-org#127 is the one to take for the registration -- it restores the four lines to their original position, keeps the crypto suites grouped, and is the position embeddedos-org#122 is already written against. Finding 2 (Medium) -- nothing would catch this coming back. Every ARM toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no cross build links an executable, so `Cross-compile ARM Cortex-M4` passed identically with the duplicate present and absent; it passed on the unfixed commit. The defect surfaced by accident when embeddedos-org#118 briefly put cmd/eos into the cross build, and embeddedos-org#118 has since gated that to host builds -- removing the accident too. Adds tests/unit/test_toolchain_specs.py, modelled on test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each -specs=<file>, and fail if one appears in more than one variable that reaches a link line (CMake links through the compiler driver, so C/CXX flags do). A second test asserts specs are declared on the linker flags rather than the compiler flags, which is the arrangement that turns into a duplicate the moment someone adds the linker entry that looks missing. No ARM toolchain needed; runs in the existing pytest job. Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken today, and one added linker -specs= away from the same failure. Moved, so all three ARM bare-metal toolchains state it the same way and the new guard is green across all of them. Verified: pytest tests/unit/test_toolchain_specs.py 2 passed pytest tests/ all pass discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than once on both tests, so the guard catches the original defect rather than restating the fix. No ARM toolchain on this machine, so the cross link itself is NOT verified here; that remains CI's to show. Refs embeddedos-org#126
Residue from 0957ac4, which dropped the borrowed test_crypto_ed25519_loworder registration and left the blank line that had separated it from the block below. It is the only remaining change this PR makes to tests/CMakeLists.txt, it changes nothing, and it sits at the exact anchor where embeddedos-org#118, embeddedos-org#119 and embeddedos-org#127 each add the real registration block -- so it would have conflicted with whichever of those lands first, for no reason at all. tests/CMakeLists.txt is now untouched by this branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every open eos pull request is currently red on `EoS Full-Stack Simulation`, and none of them caused it. The kernel job checks out embeddedos-org/eBoot at `ref: master`, and eBoot master does not compile: include/eos_image.h:135 and :142 static-assert offsetof(eos_image_header_t, reserved) for a member that became tlv_len + tlv_hash, and core/ed25519_verify.c redefines point_is_identity -- two pairs of PRs that each merged clean and broke the build together. eBoot embeddedos-org#94 repairs it and waits on review; until it lands, no change to any eos PR can make this job pass, and after it lands the next broken eBoot master does the same thing again. A floating `ref: master` makes every eos PR's CI depend on the moment-to- moment state of another repository's default branch. Pinning the two compiled-against checkouts makes the job a function of this repository plus two recorded SHAs -- red means the PR broke something, green means it did not, and a cross-repo bump is a reviewable one-line diff with CI on it. EBOOT_COMMIT a172a6d6e1e6 the newest eBoot master ancestor that compiles; found by walking master back and building each candidate. Its child 8a015b2 already carries the ed25519 redefinition. EBUILD_COMMIT e5d8052f3e2c ebuild's current master, frozen as-is. Both of its checkouts feed steps that are commented out (eFab does not exist), so this pin only removes the float. The middleware matrix (10 repos) still floats; it runs those repos' own tests rather than compiling eos against them, and 10 more pins deserve their own decision. When eBoot embeddedos-org#94 merges, bump EBOOT_COMMIT to the merge SHA. Verified before pushing, master vs pin under the job's own configuration (-DEBLDR_BOARD=qemu_arm64 -DEBLDR_VERIFY_STAGE1=OFF -DCMAKE_BUILD_TYPE=Release, cross-compile mode): eBoot@master fails at the eos_image.h asserts, the exact error in the job log; eBoot@a172a6d builds every target, rc=0. Both pinned SHAs verified reachable from their repos' master. Workflow parses; the three `ref:` edits and the env block are the whole diff. This PR's own simulation run is the live test: it uses this branch's workflow and should go green while every master-based PR stays red. Carried onto this branch from embeddedos-org#135 (001128d) so this PR's own simulation job runs against a buildable eBoot: each pull request executes its own copy of the workflow, so the fix greens a PR only once its branch contains it. Byte-identical to embeddedos-org#135's commit -- git drops it as already-upstream the moment embeddedos-org#135 merges. The pin itself is embeddedos-org#135's to review.
srpatcha
left a comment
There was a problem hiding this comment.
Review — eos#118 "fix(build): build the backend registry, and stop system.linux eating rtos"
head: f95e4cb author: Kartikey1306 ci: 25 pass, 0 failing, Create GitHub Release skipping
Verdict: The build wiring is still right and everything reproduces at this head: test build clean, ctest --no-tests=error 40/40, default build clean, and bdef/cmd/eos/eos exists and runs. CI is now fully green, which is what f95e4cb buys — and f95e4cb is a byte-identical copy of #135's cross-repo pin, the sixth open PR carrying it. The High finding from 13c20b40 is unresolved, and the commit that was meant to address it (13c20b4, "make the pop-back structural") changed the comment rather than the condition, so the code now documents an invariant it does not have.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | High | core/src/config.c:204, :218, :270, and the comment at :195-203 |
Carried from 13c20b40, unresolved, re-reproduced at this head. Both pop-back guards still test indent <= 1 against an indent_level() that is spaces / 2 (:36-40), so "system's own child level" remains a 2-space assumption. Verified with the eos binary this PR builds, on two configs with identical semantics: 2-space → RTOS targets (1), Firmware Targets (1); 4-space → RTOS targets (0), Firmware Targets (0). That is verbatim the defect in the PR title — "a hybrid build produced no RTOS firmware without saying so" — still present for any config that does not use two spaces. What 13c20b4 changed is the comment, which now argues the guard is "structural rather than a list of the seven keys" and concludes "so indent <= 1 is exactly the 'back at system's own child level' condition". It is not: it is exactly that condition given /2. A literal a reader can see is safer than a literal a comment vouches for, and the PR body is more honest than the code here — it says plainly that "indent_level() counts 2-space units, so <= 1 is exactly system:'s direct children". |
Store the indent at each descent: at every section = SEC_SYSTEM_LINUX; continue;-style site also set section_indent = indent;, then the guard is if (indent <= section_indent && …). Indentation-agnostic, removes the /2 dependency from the pop-back logic, and repairs the same literal at :270 (SEC_SYSTEM_ROOTFS on rootfs) and the indent <= 2 pair at :318-320. Add a 4-space fixture to test_firmware.c::test_config_load_hybrid — today it loads only examples/industrial-gateway/eos.yaml, which is 2-space, so nothing in the suite can see this. |
| 2 | Medium | .github/workflows/eos-simulation.yml:11-20, :41, :48, :288 |
f95e4cb ("ci: pin the eBoot and ebuild checkouts instead of floating on master") duplicates #135, and also #114's f43d860, #115's 4375466, #116's 2a32e3a and #117's f7a715b. Verified: git diff of this file between this head and #135's head is empty. Six open PRs, one CI change, and none of the six bodies mentions it. Identical hunks merge cleanly so nothing breaks — the cost is that a cross-repository dependency pin is being reviewed six times inside PRs about other subjects, and once in the PR that is actually about it. |
Drop f95e4cb. #135 is scoped to it and has the staleness-guard commit (f34edb3) these copies lack; its substance is reviewed there and is not re-raised here. |
| 3 | Medium | PR body | Carried from 13c20b40, and the specific mismatch is sharper than before. The body's section 2 says the fix is to "Put backends/include ahead of it" on the include path and that "the copy in include/ is the stale one … removing it changes no caller". The diff does neither: it keeps include/eos/backend.h (68 added, 30 removed — rewritten to the backends/ shape) and deletes backends/include/eos/backend.h (81 removed). Same end state, opposite mechanism, and a reader following the body will look for a change to an include path that is not there. The numbers are stale too (ctest 39/39 (was 38); it is 40/40 at this head), as is the opening line — "Stacked on #113 and #114", where the actual stack parent is 13a8ffa, i.e. #127. Per the brief, a body whose "Verified" table describes a tree that no longer exists is the finding, because it reads as evidence. |
Rewrite section 2 to describe what the diff does: consolidate the two divergent eos/backend.h copies into the one under include/, taking the backends/ signatures, and state that no consumer used the removed declarations (eos_backend_init_all has zero references in the tree; EosBackend::user_data had no users). §23.2 makes the source-level EoS API a versioned contract, so that paragraph is what turns an unexplained API break into a deliberate consolidation. Then re-run and paste 40/40, and fix the stack line. |
| 4 | Medium | include/eos/backend.h:1-13 |
Carried from 13c20b40, unresolved. The surviving public header still has no extern "C" guard; the deleted backends/include copy had one. .ai/architect.md calls include/ "public headers — the contract other repos compile against", and EoStudio and eBrowser are C++ clients of this tree. Verified nothing in-tree breaks — no .cpp/.cc/.hpp/.cxx translation unit includes eos/backend.h. Measured at this head: 1 of the 4 headers in include/eos/ carries the guard (package.h; backend.h, eos_config.h and quality.h do not), and this change took it from 2 to 1. |
Re-add #ifdef __cplusplus / extern "C" { around the declarations. One hunk, and it stops the ratio drifting further in a directory the architecture calls the cross-repo contract. |
Resolved since 13c20b40: prior finding 4. CMakeLists.txt:149-158 now leads with the real reason for the if(NOT CMAKE_CROSSCOMPILING) gate — "Host-only, gated the same way services/pkg is. It is a build-system driver that runs on the developer's machine" — with the nosys.specs incident as the supporting detail rather than the rationale. That is what was asked for.
Still open from 13c20b40, one line: the tests/CMakeLists.txt:93-96 test_crypto_ed25519_loworder block arrives from the stack parent 13a8ffa (#127) and is not this PR's, but #126 still adds the identical block at the identical anchor (confirmed against #126's diff at this run). Whichever of #126 and the #127 stack lands second needs a one-hunk resolution. Worth adding to the merge-order comment.
Architecture conformance
Conforms on layering; the header consolidation is an improvement; the same one process deviation.
- §5.1 /
.ai/architect.mddependency direction — conforms, and thesystems/CMakeLists.txtchange is the right correction:systems/src/firmware.cincludeseos/backend.hand callseos_backend_find(), soeos_systemsis whereeos_backendsbelongs rather than re-declared at each consumer. Nothing points up a tier —eos_backendslinkseos_corePRIVATE,eos_systemslinkseos_core eos_toolchains eos_backends,cmd/eossits above all of them, and no product-repo dependency is introduced. include/as the contract — conforms in direction. Two files namedeos/backend.hwith different struct layouts, one shadowing the other through a PUBLIC include path, is the "widen a public header to make one caller compile" failure mode inverted; collapsing to a single copy is what.ai/architect.mdasks. Findings 3 and 4 are about how, not whether.- §21 tier placement — correct.
eosis Tier 1 — Foundation; the backend registry, the systems layer and theeosCLI all belong here, and §21.1 is not engaged since nothing is being split out. .ai/architect.md, "restructure and change behaviour in the same commit" — deviates, defensibly, and still unstated in the body. Wiringbackends/+cmd/eosand changing the config parser are genuinely coupled:test_firmwareis the test that proves the wiring and cannot pass without the parser fix. The part that is not coupled is the toolchain pop-back at:218— no test needs it and no config exercises it, which the body admits ("No config underexamples/is ordered that way today, which is the only reason it has not bitten"). Finding 1 is where that untested half still fails.- Weakened checks — none. Dropping
test_firmware.cfromNOT_BUILTintests/unit/test_cmake_test_registration.pyis the opposite of a weakened check: a suite that was excused is now built and run.
No architecture proposal from this PR. The pin's design gap is already recorded in .ai/autoreview/proposals/2026-09.md.
Proposed changes
- Finding 1 — remembered section indent plus a 4-space fixture. This is the one that matters; without it the PR's headline fix covers only the indentation style the examples happen to use, and the comment now says otherwise.
- Finding 2 — drop
f95e4cb. - Finding 4 — restore the
extern "C"guard. One hunk. - Finding 3 — rewrite the body's section 2, the numbers and the stack line.
- Coordinate
tests/CMakeLists.txtwith #126.
I opened no fix PR. Finding 1 is High and the fix is small, but it can only be expressed on top of this branch — the indent <= 1 guards do not exist on master — and the brief forbids touching a branch that belongs to an open PR.
Not checked
- The 4-space failure is verified; the proposed fix is not. I did not implement the remembered-indent change, so I have not confirmed it keeps 40/40 green or that
indent <= section_indentis right at every descent site (SEC_PKG_*andSEC_LAYERSreach the guards by different paths and were not analysed). - One symptom from the previous review did NOT reproduce in my fixture. That review reported the 4-space case also showing
Toolchain: target arm-none-eabi— the RTOS target overwriting the Linux cross target. In my 4-space config, written withtoolchain.rtos.targetnested, both parsed correctly (target: aarch64-linux-gnu,rtos: arm-none-eabi). That symptom needs atarget:key written attoolchain's own indent after thertos:block, which my fixture did not have. So:218remains untested rather than shown-broken by me; only thesystemhalf at:204is reproduced here. - Tab-indented YAML not tested.
indent_level()counts spaces only, so a tab-indented file yields indent 0 for every line. YAML forbids tabs for indentation, so not treated as a finding — but nothing in the parser rejects it either. - No cross build run locally.
Cross-compile ARM Cortex-M4passing in CI is the only evidence about theNOT CMAKE_CROSSCOMPILINGgate; I did not installarm-none-eabi. - The 11 backends were not exercised.
eos_backend_registry_init()and eacheos_backend_*_init()compile and the binary runseos info; no backend'sconfigure/build/install/cleanwas invoked against a real build system, and the claim that none carries a TODO or stub marker was not verified. pytest tests/not run in this pass. The registration guard was run at13c20b40(5 passed) andtests/unit/test_cmake_test_registration.pyis unchanged between that head and this one, so its result is unchanged by inspection, not by re-running. (A uv-managedpytest 9.1.1is available on this host with the systemsite-packagesonPYTHONPATH; I used the build time on the parser reproduction instead.)eos infowas run against synthetic 2-space and 4-space configs, not against every file underexamples/; other examples may have their own indentation.- The removed public declarations were checked for in-tree callers only. Whether any other repository in the org compiles against
include/eos/backend.hwas not checked — that is the gap finding 3 asks the body to close. - The pinned eBoot tree was not built.
a172a6dexists and is an ancestor of eBoot'sorigin/master; I did not compile eos's kernel job against it, so "the pin makes CI green" rests onchecks.txt.
Evidence
Read-only git archive extraction of f95e4cb2; the eos checkout was clean and was not modified.
this head cmake -B bt -DCMAKE_BUILD_TYPE=Release -DEOS_BUILD_TESTS=ON \
-DEOS_PRODUCT=vbox_test && cmake --build -> OK
ctest --test-dir bt --no-tests=error -> 40/40 passed
cmake -B bdef -DCMAKE_BUILD_TYPE=Release && build -> OK
bdef/cmd/eos/eos -> exists, runs
finding 1, same semantics, two indentation styles, via the binary this PR builds:
2-space eos.yaml System: RTOS targets (1) --- Firmware Targets (1) ---
4-space eos.yaml System: RTOS targets (0) --- Firmware Targets (0) ---
core/src/config.c:36-40 indent_level() = spaces / 2
core/src/config.c:204 if (indent <= 1 && (section == SEC_SYSTEM_LINUX || ...
core/src/config.c:218 if (indent <= 1 && (section == SEC_TOOLCHAIN_LINUX || ...
core/src/config.c:270 if (indent <= 1 && strcmp(key, "rootfs") == 0)
core/src/config.c:318-320 if (indent <= 2 && ...) (SEC_PKG_*)
finding 2:
git diff --stat 13c20b40 f95e4cb2
-> .github/workflows/eos-simulation.yml | 12 +++, 3 --- (the entire delta)
git diff f95e4cb2 <#135 f34edb3> -- .github/workflows/eos-simulation.yml -> empty
finding 4:
include/eos/backend.h grep __cplusplus -> no match
include/eos/eos_config.h no guard
include/eos/package.h guard
include/eos/quality.h no guard
resolved (prior finding 4):
CMakeLists.txt:149 "Host-only, gated the same way services/pkg is. It is a
build-system driver that runs on the developer's machine"
Automated architecture review of f95e4cb2662d — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
…cs is passed twice (#126) * fix(build): restore the low-order Ed25519 suite that #93 removed `CI — eos` and `Python Unit Tests` are red on master (769191a), and both fail for one reason: these suites exist in tests/ but no add_executable() in tests/CMakeLists.txt builds them, so they never run: ['test_crypto_ed25519_loworder.c'] 3aa8644 (#93, "remove duplicate test targets from tests/CMakeLists.txt") removed all four lines that build and register test_crypto_ed25519_loworder. It was not a duplicate — there was exactly one registration before that commit, and it took it. What stopped running is the suite that guards #101: Ed25519 rejecting low-order public keys. A low-order key makes every term of the verification equation collapse to the identity regardless of the message, so a signature of all zeros verifies against any content at all. The fix is still in services/crypto/src/ed25519_verify.c and still correct; nothing has been checking it since #93 merged, and nothing would have noticed if a later change had removed it too. Restored verbatim from 3aa8644^, in its original position before test_crypto_sha512. No other file changes. The guard that caught this is test_cmake_test_registration.py, added recently. It did exactly its job — this is the first thing it found. Verified: test_cmake_test_registration.py -> 5 passed (1 failed on master) pytest tests/ -> 15 passed (14 passed 1 failed on master) test_crypto_ed25519_loworder -> 5/5 tests passed ctest -> 39/39 passed (master builds 38) * fix(toolchain): stop passing nosys.specs twice on a Cortex-M4 link arm-cortex-m4.cmake sets -specs=nosys.specs in CMAKE_C_FLAGS_INIT and again in CMAKE_EXE_LINKER_FLAGS_INIT. CMake links through the compiler driver, so both reach the link line and the specs file is read twice. A specs file may only be read once -- the second read tries to re-rename a spec it has already renamed: arm-none-eabi-gcc: fatal error: nosys.specs: attempt to rename spec 'link_gcc_c_sequence' to already defined spec 'nosys_link_gcc_c_sequence' It has been latent because CMAKE_TRY_COMPILE_TARGET_TYPE is STATIC_LIBRARY and nothing in the cross build links an executable, so the duplicate never reached a link line. It surfaced the moment one did. The flag belongs on the link line only; arm-none-eabi-stm32f4.cmake already keeps it there and nowhere else. This matches it. The compile line does not need it: nosys.specs supplies the syscall stubs the linker wants. Verified with arm-none-eabi-gcc rather than by reading. The mechanism, using a specs file this toolchain does ship: once: arm-none-eabi-gcc ... m.o -specs=dup.specs -o a.elf -> links twice: arm-none-eabi-gcc -specs=dup.specs m.o -specs=dup.specs -> fatal error: attempt to rename spec 'link_gcc_c_sequence' to already defined spec 'dup_link_gcc_c_sequence' the same error CI reports, from the same cause. And the generated build: before, every compile command carried -specs=nosys.specs; after, none does and the exe link flags still carry it once. The full cross build could not be run here -- the Homebrew arm-none-eabi-gcc ships no newlib, so nosys.specs and stdint.h are both absent. Configure succeeds with this change and fails without it on that toolchain, which is the part it governs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(toolchain): guard the duplicate-specs class, and align the third ARM file Answers the review on #126. Finding 1 (Medium) -- the head carried an undisclosed second change, the test_crypto_ed25519_loworder registration, which contradicted this PR's own 2026-09-02 comment ("this PR changes one file ... and does not touch tests/"). It was also the fourth open copy of that registration, at a different position from #127's. Dropped: the branch is rebased to the toolchain change alone, and #127 is the one to take for the registration -- it restores the four lines to their original position, keeps the crypto suites grouped, and is the position #122 is already written against. Finding 2 (Medium) -- nothing would catch this coming back. Every ARM toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no cross build links an executable, so `Cross-compile ARM Cortex-M4` passed identically with the duplicate present and absent; it passed on the unfixed commit. The defect surfaced by accident when #118 briefly put cmd/eos into the cross build, and #118 has since gated that to host builds -- removing the accident too. Adds tests/unit/test_toolchain_specs.py, modelled on test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each -specs=<file>, and fail if one appears in more than one variable that reaches a link line (CMake links through the compiler driver, so C/CXX flags do). A second test asserts specs are declared on the linker flags rather than the compiler flags, which is the arrangement that turns into a duplicate the moment someone adds the linker entry that looks missing. No ARM toolchain needed; runs in the existing pytest job. Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken today, and one added linker -specs= away from the same failure. Moved, so all three ARM bare-metal toolchains state it the same way and the new guard is green across all of them. Verified: pytest tests/unit/test_toolchain_specs.py 2 passed pytest tests/ all pass discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than once on both tests, so the guard catches the original defect rather than restating the fix. No ARM toolchain on this machine, so the cross link itself is NOT verified here; that remains CI's to show. Refs #126 * test(toolchains): see the spec wherever it is written, and drop the borrowed hunk Addresses findings 1-4 of the review on 126bd0c. Finding 4 first, because it is the one that was making the branch look better than it is. The test_crypto_ed25519_loworder registration was carried here from #127. It made Run Python tests green on a branch that has nothing to do with it, contradicted this PR's own 2026-09-02 comment, and sat at a different anchor to #127's, so it would have conflicted. Dropped. That check is red again until #127 lands, which is the honest state -- folding a master repair into a toolchain PR to get a green tick is how a red master becomes everyone's problem to route around. Finding 1: the parser recognised one CMake idiom, set(VAR "literal"). It could not see string(APPEND ...), an unquoted set(), or -- the one that matters here -- a spec arriving through ${CPU_FLAGS}, which is how both Cortex-M4 toolchains build their flag strings. Putting -specs= there would have reached both CMAKE_C_FLAGS_INIT and CMAKE_EXE_LINKER_FLAGS_INIT while every check passed. The file is now walked in order with a variable table, ${...} resolved against what is in force at that point, and string(APPEND ...) applied. Two tests pin those two bypasses. Finding 3: CMAKE_ASM_FLAGS_INIT added to LINK_REACHING. CMake picks a target's linker language from its sources, so an executable built only from .S files links with the ASM flags. Both Cortex-M4 files set it from ${CPU_FLAGS}. The current tree still passes; no toolchain names a spec there today. Finding 2: the .yaml descriptors are scanned too. They are the second authority on these flags -- toolchains/src/toolchain.c parses them into EosToolchain.cflags/ldflags, a separate consumer from CMake -- and all five already keep specs in ldflags, which is a good argument for this change and a better one for holding both descriptions to one rule. Found while verifying rather than assuming: my first regex required the closing paren on the same line, so arm-none-eabi-stm32f4.cmake's three-line set(CMAKE_EXE_LINKER_FLAGS_INIT ...) was invisible and every check passed vacuously for that file. That is this test's own failure mode one level up. Fixed, and pinned by test_a_multi_line_set_is_still_seen and test_every_arm_toolchain_is_actually_parsed -- the latter asserts the parser finds specs in all three ARM files, so a parser that silently sees nothing can no longer pass. Verified: the parser reports arm-cortex-m4 [nosys, nano], arm-none-eabi-r5 [nosys], arm-none-eabi-stm32f4 [nano, nosys], all on CMAKE_EXE_LINKER_FLAGS_INIT. Mutations caught: a spec hidden in CPU_FLAGS, and the original duplicate re-introduced on CMAKE_C_FLAGS_INIT. 7 toolchain tests pass, ctest 38/38. * test(toolchains): drop the blank line the removed hunk left behind Residue from 0957ac4, which dropped the borrowed test_crypto_ed25519_loworder registration and left the blank line that had separated it from the block below. It is the only remaining change this PR makes to tests/CMakeLists.txt, it changes nothing, and it sits at the exact anchor where #118, #119 and #127 each add the real registration block -- so it would have conflicted with whichever of those lands first, for no reason at all. tests/CMakeLists.txt is now untouched by this branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(build): take the loworder registration from #127, not a copy of it Restacked onto #127. The previous head dropped the registration hunk entirely, per finding 4, which left Run Python tests red by design until #127 landed. Stacking gets the same result without a duplicate: the hunk comes from #127, this branch adds none of its own, and the diff against #127 is toolchain-only. Verified locally before pushing: pytest 22 passed (including test_every_c_suite_is_built_or_listed, the check that was failing), cmake configure and build clean, ctest 39/39 with test_crypto_ed25519_loworder among them. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nine PRs were merged into master within minutes on 09-08, each on the base it was written against. Master has not compiled since, and the Python guards that would have named the rest never ran because the C build failed first. Compile (every C job, CodeQL, and the ARM64 kernel in the simulation): - services/linux/src/linux_security.c: embeddedos-org#119 and embeddedos-org#132 each added an #else to the same #ifndef _WIN32 in eos_busybox_install_to_rootfs(), so master has "#else after #else". The embeddedos-org#132 arm (`(void)bb;`) is the one removed -- the embeddedos-org#119 arm already uses bb and reports the unsupported platform. Guards from embeddedos-org#121 / embeddedos-org#93 that later merges walked back: - ci.yml: embeddedos-org#132 added windows-test after embeddedos-org#121's gate; the gate did not wait for it, so "CI Gate" could be green with the MSVC leg red. - test_ci_gate.py: embeddedos-org#129 gave eosim-sanity.yml and simulation-test.yml a path-filtered pull_request trigger (they test their own edits); a path-filtered check cannot be required, so both are recorded in NOT_REQUIRED with the book-build.yml reason. - tests/CMakeLists.txt: test_linux_security_paths (embeddedos-org#119) and test_pkg_fetch (embeddedos-org#115) had no add_executable(). embeddedos-org#115's replay replaced embeddedos-org#119's block with its own, and embeddedos-org#118's replay replaced that; two suites compiled against nothing. Both registered again. 41 -> 43 suites. - tests/test_kernel.c: four tests from embeddedos-org#130 and embeddedos-org#131 were defined and never called -- their RUN() lines did not survive the replay of main(). And the one that was not bookkeeping: - kernel/src/task.c: embeddedos-org#130 was merged after embeddedos-org#131 from a base that predates it, and its copy of task.c replaced embeddedos-org#131's. embeddedos-org#131 had also flattened 574 CRLF line endings, so its 1166-line diff hid a 39/21 change and the replay took embeddedos-org#130's side wholesale. Master kept embeddedos-org#131's tests and lost its kernel: the idle task could be deleted and suspended, a half-initialised TCB was published to the scheduler before its stack existed, and eos_schedule() pointed g_current_sp at the outgoing task. test_idle_task_is_permanent fails on master the moment it is called. embeddedos-org#131's task.c diff re-applied on top of embeddedos-org#130's; the result is embeddedos-org#131's file plus embeddedos-org#130's wake_armed hunk and nothing else (verified by diff against 3a00bd9). Verified locally (macOS, clang): Release build clean, 43/43 ctest; the README default configuration builds; 47/47 pytest. Not in this PR: the nightly "Upstream drift" job builds eBoot at master and eBoot master is broken separately (its own repair PR); bump EBOOT_COMMIT in eos-simulation.yml once that lands.
…ARM file Answers the review on embeddedos-org#126. Finding 1 (Medium) -- the head carried an undisclosed second change, the test_crypto_ed25519_loworder registration, which contradicted this PR's own 2026-09-02 comment ("this PR changes one file ... and does not touch tests/"). It was also the fourth open copy of that registration, at a different position from embeddedos-org#127's. Dropped: the branch is rebased to the toolchain change alone, and embeddedos-org#127 is the one to take for the registration -- it restores the four lines to their original position, keeps the crypto suites grouped, and is the position embeddedos-org#122 is already written against. Finding 2 (Medium) -- nothing would catch this coming back. Every ARM toolchain here sets CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY and no cross build links an executable, so `Cross-compile ARM Cortex-M4` passed identically with the duplicate present and absent; it passed on the unfixed commit. The defect surfaced by accident when embeddedos-org#118 briefly put cmd/eos into the cross build, and embeddedos-org#118 has since gated that to host builds -- removing the accident too. Adds tests/unit/test_toolchain_specs.py, modelled on test_cmake_test_registration.py: parse every toolchains/*.cmake, extract each -specs=<file>, and fail if one appears in more than one variable that reaches a link line (CMake links through the compiler driver, so C/CXX flags do). A second test asserts specs are declared on the linker flags rather than the compiler flags, which is the arrangement that turns into a duplicate the moment someone adds the linker entry that looks missing. No ARM toolchain needed; runs in the existing pytest job. Finding 3 (Low) -- arm-none-eabi-r5.cmake put --specs=nosys.specs in CMAKE_C_FLAGS_INIT with nothing in CMAKE_EXE_LINKER_FLAGS_INIT. Not broken today, and one added linker -specs= away from the same failure. Moved, so all three ARM bare-metal toolchains state it the same way and the new guard is green across all of them. Verified: pytest tests/unit/test_toolchain_specs.py 2 passed pytest tests/ all pass discrimination: reintroducing the duplicate in arm-cortex-m4.cmake gives arm-cortex-m4.cmake: -specs=nosys.specs is set in CMAKE_C_FLAGS_INIT and CMAKE_EXE_LINKER_FLAGS_INIT, so it reaches the link line more than once on both tests, so the guard catches the original defect rather than restating the fix. No ARM toolchain on this machine, so the cross link itself is NOT verified here; that remains CI's to show. Refs embeddedos-org#126
Residue from 0957ac4, which dropped the borrowed test_crypto_ed25519_loworder registration and left the blank line that had separated it from the block below. It is the only remaining change this PR makes to tests/CMakeLists.txt, it changes nothing, and it sits at the exact anchor where embeddedos-org#118, embeddedos-org#119 and embeddedos-org#127 each add the real registration block -- so it would have conflicted with whichever of those lands first, for no reason at all. tests/CMakeLists.txt is now untouched by this branch.
Three findings, one chain — the second was hidden behind the first, and the third behind the second.
1.
backends/was in noadd_subdirectory()Its 12 sources were compiled by nothing, so
libeos_backends.anever existed.eos_systems'firmware.ccallseos_backend_find()andeos_backend_find_by_type(), so anything linkingeos_systemsfailed to link.2. Adding it didn't fix it — and the reason was not what it looked like
That reads as a subsystem that has drifted out of date with the API. It has not. There are two headers named
eos/backend.h:include/eos/backend.hint-returningbackends/include/eos/backend.hEosResult-returninginclude/comes first on the global include path, so every translation unit got the wrong one. Putbackends/includeahead of it and all 12 sources compile clean.The copy in
include/is the stale one:eos_backend_init_all(), which no.cdefineseos_backend_registry_init()and theeos_backend_*_init()functions thatcmd/eos/main.ccallseos_backend_find()andeos_backend_find_by_type()— all thatfirmware.candscheduler.cuseSo removing it changes no caller. That also closes one of the undefined-declaration findings from #114.
3. With backends built,
test_firmwarelinks — and immediately abortsIt was excluded in #114 for exactly this reason. Registered, it fails at once:
against
examples/industrial-gateway/eos.yaml, which plainly declares one:Parsing
system.linuxmoves toSEC_SYSTEM_LINUX, which handlesprovider,kernelandrootfsand has no way back to the parent — so the siblingrtos:is swallowed.rtos_countstays 0, and a hybrid build produces no RTOS firmware while reporting nothing.SEC_SYSTEM_KERNELalready popped back for one specific sibling (indent <= 1 && key == "rootfs"). This pops back for any key atsystem's own child indent, which is what the indentation means —indent_level()counts 2-space units, so<= 1is exactlysystem:'s direct children and never an rtos entry's keys at indent 3.Verified
test_firmwareadded, 11/11 inside ittest_firmwareaborts onrtos_count >= 1, exit 134test_firmware.cis also dropped fromNOT_BUILTin the registration guard, whosetest_not_built_list_has_no_stale_entriescase would otherwise fail — which is the guard doing its job.