Fix schematic validation to scan subdirectories for hierarchical sheets

Hierarchical KiCad projects store sub-sheets in subdirectories (e.g.
sheets/). The flat os.listdir scan missed all of them. Use recursive
glob to find .kicad_sch files at any depth under the project directory.

Reported by ESP32-P4 project (agent thread message 025) — their 8
malformed property-private entries were all in sheets/ subdirectory.
This commit is contained in:
Ryan Malloy 2026-03-05 11:27:42 -07:00
parent 56705cf345
commit c1ddf0c5f7
2 changed files with 50 additions and 7 deletions

View File

@ -6,6 +6,7 @@ detail retrieval into a single module. Uses KiCad IPC when available
for live data, falling back to file-based checks otherwise. for live data, falling back to file-based checks otherwise.
""" """
import glob
import json import json
import logging import logging
import os import os
@ -188,15 +189,13 @@ def validate_project(project_path: str) -> dict[str, Any]:
except Exception as e: except Exception as e:
issues.append(f"Error reading project file: {e}") issues.append(f"Error reading project file: {e}")
# Validate schematic sexp integrity (all .kicad_sch files in project dir) # Validate schematic sexp integrity (all .kicad_sch files, including sub-sheets)
if "schematic" in files: if "schematic" in files:
project_dir = os.path.dirname(project_path) project_dir = os.path.dirname(project_path)
sch_files = [ sch_files = sorted(
os.path.join(project_dir, f) glob.glob(os.path.join(project_dir, "**", "*.kicad_sch"), recursive=True)
for f in os.listdir(project_dir) )
if f.endswith(".kicad_sch") for sch_path in sch_files:
]
for sch_path in sorted(sch_files):
issues.extend(_validate_schematic_sexp(sch_path)) issues.extend(_validate_schematic_sexp(sch_path))
# Optional live analysis via KiCad IPC # Optional live analysis via KiCad IPC

View File

@ -1,5 +1,6 @@
"""Tests for project analysis and validation tools.""" """Tests for project analysis and validation tools."""
import json
import os import os
import textwrap import textwrap
@ -134,3 +135,46 @@ class TestValidateSchematicSexp:
path = self._write_sch(tmp_output_dir, "empty.kicad_sch", content) path = self._write_sch(tmp_output_dir, "empty.kicad_sch", content)
issues = _validate_schematic_sexp(path) issues = _validate_schematic_sexp(path)
assert issues == [] assert issues == []
def test_subdirectory_sheets_scanned(self, tmp_output_dir):
"""validate_project scans .kicad_sch files in subdirectories."""
from mckicad.tools.analysis import validate_project
# Create a minimal project structure with a sub-sheet in sheets/
pro_path = os.path.join(tmp_output_dir, "test.kicad_pro")
with open(pro_path, "w") as f:
json.dump({}, f)
# Root schematic — clean
root_sch = os.path.join(tmp_output_dir, "test.kicad_sch")
with open(root_sch, "w") as f:
f.write(textwrap.dedent("""\
(kicad_sch (version 20231120) (generator "eeschema")
(lib_symbols)
)
"""))
# Sub-sheet in sheets/ — has malformed property private
sheets_dir = os.path.join(tmp_output_dir, "sheets")
os.makedirs(sheets_dir)
sub_sch = os.path.join(sheets_dir, "sub.kicad_sch")
with open(sub_sch, "w") as f:
f.write(textwrap.dedent("""\
(kicad_sch (version 20231120) (generator "eeschema")
(lib_symbols
(symbol "Device:Crystal_GND24"
(property "private" "KLC_S3.3" malformed
(at 0 0 0)
(effects (font (size 1.27 1.27)) (hide yes))
)
)
)
)
"""))
result = validate_project(pro_path)
# Should find the malformation in the sub-sheet
issues = result.get("issues") or []
sexp_issues = [i for i in issues if "private" in i]
assert len(sexp_issues) == 1
assert "sub.kicad_sch" in sexp_issues[0]