Repository navigation
refactor(cli): share one YAML-mapping reader for eos.yaml and board.yaml #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -508,15 +508,28 @@ def _selected_board(default: str = "generic") -> str: | |
| the documented walk, so the value has to survive from `ebuild new` or | ||
| `ebuild configure`. | ||
| """ | ||
| path = Path("eos.yaml") | ||
| data = _read_yaml_mapping(Path(_EOS_PROJECT_CONFIG)) or {} | ||
| system = data.get("system") | ||
| board = system.get("board") if isinstance(system, dict) else None | ||
| return board or default | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Build fails after linking When eos.yaml has a truthy non-string system.board and footprint measurement is available, Validate that Also at Prompt for AI agentsIn `ebuild/cli/commands.py` at line 514:
**Build fails after linking**
When eos.yaml has a truthy non-string system.board and footprint measurement is available, `_selected_board` returns the value unchanged, so `_report_footprint` passes it to `board_capacity`, where `board.lower()` raises `AttributeError`; the build fails after linking.
Validate that `system.board` is a string before returning it.
Also at `ebuild/cli/commands.py:515`.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Invalid configuration appears valid When eos.yaml is malformed or has a non-mapping top-level or system value, and footprint measurement is available, Report invalid configuration and avoid presenting the fallback board or readiness when capacity cannot be verified. Also at Prompt for AI agentsIn `ebuild/cli/commands.py` at line 514:
**Invalid configuration appears valid**
When eos.yaml is malformed or has a non-mapping top-level or system value, and footprint measurement is available, `_selected_board` silently substitutes the default, so `_build_summary` presents a fallback board without reporting the configuration problem and `_report_footprint` skips the capacity check while reporting readiness.
Report invalid configuration and avoid presenting the fallback board or readiness when capacity cannot be verified.
Also at `ebuild/cli/commands.py:515`, `ebuild/cli/commands.py:532`.From - Never fail silently. A swallowed error is a defect that will be found later
and cost more. |
||
|
|
||
|
|
||
| def _read_yaml_mapping(path: Path) -> Optional[Dict[str, Any]]: | ||
| """The top-level mapping in ``path``, or None if there is not one. | ||
|
|
||
| For optional project files read after a build has already succeeded: | ||
| a missing, unreadable, malformed or non-mapping file is treated as | ||
| absent rather than raised, because callers index into the result and | ||
| a traceback there would fail a build that linked. | ||
| """ | ||
| if not path.is_file(): | ||
| return default | ||
| return None | ||
| try: | ||
| import yaml | ||
| data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} | ||
| data = yaml.safe_load(path.read_text(encoding="utf-8")) | ||
| except Exception: | ||
| return default | ||
| return (data.get("system") or {}).get("board") or default | ||
| return None | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Concurrent configuration hides board When Write the updated YAML to a temporary file and atomically replace Prompt for AI agentsIn `ebuild/cli/commands.py` at line 531:
**Concurrent configuration hides board**
When `ebuild configure --board` runs concurrently with a build that is reading `eos.yaml`, `_record_board_selection` truncates and rewrites `eos.yaml` while `_read_yaml_mapping` reads it; if the build reads during that write, the parse exception is swallowed and `_build_summary` reports no board while `_report_footprint` omits the board capacity, potentially suppressing a fit warning for the linked image.
Write the updated YAML to a temporary file and atomically replace `eos.yaml` so readers see either the old or new complete mapping. |
||
| return data if isinstance(data, dict) else None | ||
|
|
||
|
|
||
| def _build_summary(cfg: "ProjectConfig", compiler, package_paths, log: Logger) -> None: | ||
|
|
@@ -622,15 +635,7 @@ def _board_config() -> Optional[Dict[str, Any]]: | |
| A project that states its part's real capacity should not be measured | ||
| against the reference part for its family. | ||
| """ | ||
| path = Path("board.yaml") | ||
| if not path.is_file(): | ||
| return None | ||
| try: | ||
| import yaml | ||
| data = yaml.safe_load(path.read_text(encoding="utf-8")) | ||
| except Exception: | ||
| return None | ||
| return data if isinstance(data, dict) else None | ||
| return _read_yaml_mapping(Path("board.yaml")) | ||
|
|
||
|
|
||
| def _configure_ninja_backend( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| """ebuild init / sim / platforms: the getting-started guide's commands.""" | ||
| from __future__ import annotations | ||
|
|
||
| import shlex | ||
| import stat | ||
| import sys | ||
| import textwrap | ||
|
|
@@ -15,9 +16,19 @@ | |
|
|
||
|
|
||
| def _fake_qemu(tmp_path: Path, body: str) -> str: | ||
| """A stand-in qemu: a Python script that prints like a guest would.""" | ||
| """A stand-in qemu: a Python script that prints like a guest would. | ||
|
|
||
| Launched through a /bin/sh wrapper rather than a `#!{sys.executable}` | ||
| shebang, because a shebang cannot carry an interpreter path containing | ||
| spaces (e.g. a checkout under "My Projects/"), and the exec then fails | ||
| with ENOENT before the test exercises anything. | ||
| """ | ||
| guest = tmp_path / "fake_guest.py" | ||
| guest.write_text(textwrap.dedent(body), encoding="utf-8") | ||
| script = tmp_path / "fake-qemu" | ||
| script.write_text(f"#!{sys.executable}\n" + textwrap.dedent(body), encoding="utf-8") | ||
| script.write_text( | ||
| f'#!/bin/sh\nexec {shlex.quote(sys.executable)} {shlex.quote(str(guest))} "$@"\n', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| encoding="utf-8") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Space-path regressions go undetected When Exercise the fixture with interpreter and guest paths containing spaces, or otherwise assert that the generated wrapper safely quotes such paths. Prompt for AI agentsIn `tests/unit/test_golden_path.py` at line 31:
**Space-path regressions go undetected**
When `sys.executable` and `tmp_path` contain no spaces, the `TestRunQemu` tests use the environment’s actual `sys.executable` and `tmp_path`; with ordinary space-free paths, they still pass if this command loses its quoting, so the fixture’s space-path failure returns unnoticed.
Exercise the fixture with interpreter and guest paths containing spaces, or otherwise assert that the generated wrapper safely quotes such paths. |
||
| script.chmod(script.stat().st_mode | stat.S_IEXEC) | ||
| return str(script) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,19 @@ def test_board_defaults_when_unset(self, tmp_path, monkeypatch): | |
| monkeypatch.chdir(tmp_path) | ||
| assert _selected_board() == "generic" | ||
|
|
||
| @pytest.mark.parametrize("text", [ | ||
| "- stm32f4\n", # top level is a list | ||
| "system: stm32f4\n", # system is a string, not a mapping | ||
| "system: {board: [unclosed\n", # not valid YAML | ||
| ]) | ||
| def test_a_malformed_eos_yaml_falls_back_instead_of_raising( | ||
| self, tmp_path, monkeypatch, text): | ||
| """`ebuild build` reads the board back after the image has linked, to | ||
| print its summary. A traceback there would fail a successful build.""" | ||
| monkeypatch.chdir(tmp_path) | ||
| Path("eos.yaml").write_text(text, encoding="utf-8") | ||
| assert _selected_board() == "generic" | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚪ Low · CLI regressions escape this test The new test calls Exercise the malformed-configuration behavior through the public build command. Prompt for AI agentsIn `tests/unit/test_golden_path_commands.py` at line 91:
**CLI regressions escape this test**
The new test calls `_selected_board` directly instead of exercising the public build command, so it does not verify that malformed `eos.yaml` is handled through the user-facing build flow.
Exercise the malformed-configuration behavior through the public build command.From Test behaviour through the public interface, not private internals. |
||
| def test_board_without_a_project_is_an_error(self, tmp_path, monkeypatch): | ||
| """Refuse rather than write an eos.yaml into whatever directory the | ||
| developer happened to be standing in.""" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. Board name can be a non-string value
🐞 Bug⚙ MaintainabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation toolsDismiss ↗ | View ↗