From 849b9c01a843148bb7d42209c5264a3788778626 Mon Sep 17 00:00:00 2001 From: hshanmug12 Date: Mon, 13 Apr 2026 22:51:00 -0700 Subject: [PATCH 1/2] Fix BOM in EoS AI test file --- tests/ebuild/test_eos_ai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ebuild/test_eos_ai.py b/tests/ebuild/test_eos_ai.py index e6fb922..33f27d3 100644 --- a/tests/ebuild/test_eos_ai.py +++ b/tests/ebuild/test_eos_ai.py @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: MIT +# SPDX-License-Identifier: MIT # Copyright (c) 2026 EoS Project """Unit tests for ebuild EoS AI module. From a9cf58bd36440b11564ab9d64f87697e3dd6b73b Mon Sep 17 00:00:00 2001 From: hshanmug12 Date: Thu, 16 Apr 2026 12:28:36 -0700 Subject: [PATCH 2/2] fix: resolve 7 bugs in graph, config-gen, rootfs, and CLI - graph.py: remove 13 lines of dead code in topological_sort() (first in_degree dict + setdefault loop were overwritten immediately) - eos_config_generator.py: enforce 8 KiB floor on stage0_size so 128 KiB MCUs don't silently produce a 4 KiB bootloader slot - eos_config_generator.py: add arch-aware _flash_base_for() helper so nRF52 (0x00000000) and RP2040 (0x10000000) get correct bases instead of the STM32-only hardcoded 0x08000000 - eos_config_generator.py: expand toolchain prefix map to cover arm64, aarch64, riscv64, x86_64, mips; remove dead 'if ble: pass' no-op - rootfs.py: reject home paths containing '..' to prevent directory traversal out of the rootfs sandbox - commands.py: replace str.replace('\', '/') with Path.as_posix() for correct cross-platform CMake path generation - commands.py: raise BuildError when a recipe's declared dependency was not built, instead of silently omitting it from dep_dirs All 55 tests pass (23 unit + 32 integration) in WSL Ubuntu. Co-Authored-By: Claude Sonnet 4.6 --- ebuild/cli/commands.py | 13 ++++++++--- ebuild/core/graph.py | 13 ----------- ebuild/eos_ai/eos_config_generator.py | 33 +++++++++++++++++++++++---- ebuild/system/rootfs.py | 5 +++- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/ebuild/cli/commands.py b/ebuild/cli/commands.py index ee8b13e..d961ceb 100644 --- a/ebuild/cli/commands.py +++ b/ebuild/cli/commands.py @@ -111,7 +111,14 @@ def _install_packages( fetcher.fetch(recipe, cache.src_dir(recipe)) log.step(f" Building {recipe.name} v{recipe.version}...") - dep_dirs = [install_dirs[dep] for dep in recipe.dependencies if dep in install_dirs] + dep_dirs = [] + for dep in recipe.dependencies: + if dep not in install_dirs: + raise BuildError( + f"Dependency '{dep}' of '{recipe.name}' was not built. " + "Check that all recipes are available." + ) + dep_dirs.append(install_dirs[dep]) install_dir = builder.build(recipe, dep_install_dirs=dep_dirs) install_dirs[recipe.name] = install_dir log.success(f" {recipe.name} v{recipe.version} — built ✓") @@ -261,12 +268,12 @@ def _run_cmake_build(profile, board, source_dir, build_dir, log): # Point cmake to generated config headers gen_include = build_dir / "include" / "generated" if gen_include.exists(): - cmake_defines["EOS_GENERATED_INCLUDE_DIR"] = str(gen_include).replace("\\", "/") + cmake_defines["EOS_GENERATED_INCLUDE_DIR"] = gen_include.as_posix() # Point to eboot cmake defs if present eboot_cmake = build_dir / "configs" / "eboot_config.cmake" if eboot_cmake.exists(): - cmake_defines["EBOOT_CONFIG_FILE"] = str(eboot_cmake).replace("\\", "/") + cmake_defines["EBOOT_CONFIG_FILE"] = eboot_cmake.as_posix() log.step("[6/6] Building with cmake...") log.info(" Defines: " + str(len(cmake_defines)) + " cmake variables") diff --git a/ebuild/core/graph.py b/ebuild/core/graph.py index 8cd6cd2..758602e 100644 --- a/ebuild/core/graph.py +++ b/ebuild/core/graph.py @@ -71,19 +71,6 @@ def topological_sort(self) -> List[str]: Uses Kahn's algorithm. Raises CycleError if a cycle exists. """ in_degree: Dict[str, int] = {n: 0 for n in self._nodes} - for node, deps in self._adj.items(): - for dep in deps: - in_degree.setdefault(node, 0) - in_degree.setdefault(dep, 0) - - # in_degree[x] = number of nodes that depend on x? - # Actually we want: in_degree[x] = number of dependencies x has - # For Kahn's we need: in_degree[x] = number of edges pointing INTO x - # Edge: dependent -> dependency means "dependent needs dependency" - # For build order, dependency must come first. - # Re-interpret: edges as dependency -> dependent (reverse adj for Kahn's) - - in_degree = {n: 0 for n in self._nodes} reverse_adj: Dict[str, Set[str]] = defaultdict(set) for dependent, deps in self._adj.items(): diff --git a/ebuild/eos_ai/eos_config_generator.py b/ebuild/eos_ai/eos_config_generator.py index f9d2c57..52ee7bd 100644 --- a/ebuild/eos_ai/eos_config_generator.py +++ b/ebuild/eos_ai/eos_config_generator.py @@ -19,6 +19,23 @@ from ebuild.eos_ai.eos_hw_analyzer import HardwareProfile +_FLASH_BASE_MAP = { + "nrf": "0x00000000", + "rp2040": "0x10000000", + "esp32": "0x00000000", +} + + +def _flash_base_for(profile: "HardwareProfile") -> str: + """Return the correct flash base address for this MCU.""" + key = (profile.mcu or "").lower() + family = (profile.mcu_family or "").lower() + for prefix, base in _FLASH_BASE_MAP.items(): + if key.startswith(prefix) or family.startswith(prefix): + return base + return "0x08000000" + + class EosConfigGenerator: """Generates build/boot/OS configs from a HardwareProfile.""" @@ -67,7 +84,7 @@ def generate_board_yaml(self, profile: HardwareProfile) -> Path: def generate_boot_yaml(self, profile: HardwareProfile) -> Path: """Generate eboot-compatible boot configuration.""" flash = profile.flash_size or 1024 * 1024 - stage0_size = min(16 * 1024, flash // 32) + stage0_size = max(8 * 1024, min(16 * 1024, flash // 32)) stage1_size = min(64 * 1024, flash // 8) bootctl_size = 4096 slot_size = (flash - stage0_size - stage1_size - bootctl_size * 2) // 2 @@ -76,7 +93,7 @@ def generate_boot_yaml(self, profile: HardwareProfile) -> Path: "boot": { "board": profile.mcu.lower() or "custom", "arch": profile.arch, - "flash_base": "0x08000000", + "flash_base": _flash_base_for(profile), "flash_size": flash, "layout": { "stage0": {"offset": "0x00000000", "size": stage0_size}, @@ -118,9 +135,15 @@ def generate_boot_yaml(self, profile: HardwareProfile) -> Path: def generate_build_yaml(self, profile: HardwareProfile) -> Path: """Generate ebuild build.yaml for the project.""" - toolchain = "arm-none-eabi" if profile.arch == "arm" else "gcc" - if profile.has_peripheral("ble"): - pass + _TOOLCHAIN_PREFIX = { + "arm": "arm-none-eabi", + "arm64": "aarch64-linux-gnu", + "aarch64": "aarch64-linux-gnu", + "riscv64": "riscv64-linux-gnu", + "x86_64": "x86_64-linux-gnu", + "mips": "mipsel-linux-gnu", + } + toolchain = _TOOLCHAIN_PREFIX.get(profile.arch, "gcc") build = { "project": { diff --git a/ebuild/system/rootfs.py b/ebuild/system/rootfs.py index 2eed6ec..a467cac 100644 --- a/ebuild/system/rootfs.py +++ b/ebuild/system/rootfs.py @@ -91,7 +91,10 @@ def configure_users( passwd_lines.append(f"{name}:x:{uid}:{uid}:{name}:{home}:{shell}\n") group_lines.append(f"{name}:x:{uid}:\n") shadow_lines.append(f"{name}::0:0:99999:7:::\n") - (self.rootfs_dir / home.lstrip("/")).mkdir(parents=True, exist_ok=True) + safe_home = os.path.normpath(home.lstrip("/")) + if ".." in safe_home.split(os.sep): + raise RootfsError(f"Invalid home path for user {name!r}: {home!r}") + (self.rootfs_dir / safe_home).mkdir(parents=True, exist_ok=True) uid += 1 (self.rootfs_dir / "etc" / "passwd").write_text("".join(passwd_lines))