From 1dca4d6912f7239c2be4e56294a359155a952995 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Sat, 11 Jul 2026 17:09:34 -0600 Subject: [PATCH] Fix symbol library resolution on macOS and without a sym-lib-table Two bugs left built-in symbols (Device, Connector, ...) unresolvable, which cascaded into every batch place/label/wire producing zero results: - get_kicad_symbol_dir() only checked KICAD9/KICAD8_SYMBOL_DIR and a single Linux path. It ignored the generic KICAD_SYMBOL_DIR, KiCad 10, and the macOS/Windows install locations, so it returned None on macOS even with the symbols present. Now checks KICAD_SYMBOL_DIR + KICAD{10,9,8,7}_SYMBOL_DIR and per-platform default paths. - resolve_library_path() never searched the system symbol directory for .kicad_sym; the system dir was only used for variable substitution inside a sym-lib-table. With no table configured (tests, fresh installs), built-in libraries were unreachable. Added a system-dir fallback and covered the KiCad 10/7 global-table config dirs. Update test_env_override to clear ambient symbol-dir vars so the override under test is decisive regardless of the host environment. --- src/mckicad/utils/lib_resolver.py | 45 ++++++++++++++++++++++++------- tests/test_lib_resolver.py | 16 ++++++++--- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/mckicad/utils/lib_resolver.py b/src/mckicad/utils/lib_resolver.py index 38ff381..d4abc83 100644 --- a/src/mckicad/utils/lib_resolver.py +++ b/src/mckicad/utils/lib_resolver.py @@ -106,9 +106,12 @@ def resolve_library_path( Search order: 1. Project sym-lib-table (if project_dir given) - 2. Global sym-lib-table (KiCad 9, then 8) + 2. Global sym-lib-table (KiCad 10, 9, 8, 7) 3. Direct file search (project dir, libs/, ../libs/) + 4. System symbol directory (built-in libraries like Device) """ + filename = f"{library_name}.kicad_sym" + # 1. Project sym-lib-table if project_dir: table_path = os.path.join(project_dir, "sym-lib-table") @@ -119,8 +122,8 @@ def resolve_library_path( if os.path.isfile(uri): return uri - # 2. Global sym-lib-tables (KiCad 9 first, then 8) - for version_dir in ("9.0", "8.0"): + # 2. Global sym-lib-tables (newest KiCad first) + for version_dir in ("10.0", "9.0", "8.0", "7.0"): config_dir = os.path.expanduser(f"~/.config/kicad/{version_dir}") table_path = os.path.join(config_dir, "sym-lib-table") if os.path.isfile(table_path): @@ -132,7 +135,6 @@ def resolve_library_path( # 3. Direct file search (fallback for projects without sym-lib-table) if project_dir: - filename = f"{library_name}.kicad_sym" candidates = [ os.path.join(project_dir, filename), os.path.join(project_dir, "libs", filename), @@ -143,6 +145,14 @@ def resolve_library_path( if os.path.isfile(resolved): return resolved + # 4. System symbol directory (built-in libraries, e.g. Device, Connector). + # This is the authoritative source when no sym-lib-table is configured. + symbol_dir = get_kicad_symbol_dir() + if symbol_dir: + resolved = os.path.join(symbol_dir, filename) + if os.path.isfile(resolved): + return resolved + return None @@ -249,16 +259,33 @@ def extract_symbol_sexp_text( def get_kicad_symbol_dir() -> str | None: """Detect the KiCad system symbol directory. - Checks ``KICAD9_SYMBOL_DIR`` and ``KICAD8_SYMBOL_DIR`` env vars, - then falls back to the standard Linux install path. + Checks, in order, the generic ``KICAD_SYMBOL_DIR`` env var, the + version-specific ``KICAD{10,9,8,7}_SYMBOL_DIR`` vars, then the standard + install paths for macOS, Linux, and Windows. """ - for env_var in ("KICAD9_SYMBOL_DIR", "KICAD8_SYMBOL_DIR"): + for env_var in ( + "KICAD_SYMBOL_DIR", + "KICAD10_SYMBOL_DIR", + "KICAD9_SYMBOL_DIR", + "KICAD8_SYMBOL_DIR", + "KICAD7_SYMBOL_DIR", + ): val = os.environ.get(env_var) if val and os.path.isdir(val): return val - # Standard paths - for path in ("/usr/share/kicad/symbols",): + # Standard per-platform install paths + candidates = [ + # macOS + "/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols", + # Linux + "/usr/share/kicad/symbols", + "/usr/local/share/kicad/symbols", + # Windows + r"C:\Program Files\KiCad\10.0\share\kicad\symbols", + r"C:\Program Files\KiCad\9.0\share\kicad\symbols", + ] + for path in candidates: if os.path.isdir(path): return path diff --git a/tests/test_lib_resolver.py b/tests/test_lib_resolver.py index 6946637..81e2ed6 100644 --- a/tests/test_lib_resolver.py +++ b/tests/test_lib_resolver.py @@ -17,7 +17,6 @@ from mckicad.utils.lib_resolver import ( ) from mckicad.utils.sexp_tree import find_recursive, parse - # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @@ -69,7 +68,7 @@ def project_with_lib(tmp_path): "(sym_lib_table\n" " (version 7)\n" ' (lib (name "MyLib")(type "KiCad")' - f'(uri "${{KIPRJMOD}}/MyLib.kicad_sym")(options "")(descr ""))\n' + '(uri "${KIPRJMOD}/MyLib.kicad_sym")(options "")(descr ""))\n' ")\n" ) table_file = tmp_path / "sym-lib-table" @@ -138,7 +137,7 @@ def multi_unit_lib(tmp_path): "(sym_lib_table\n" " (version 7)\n" ' (lib (name "OpAmps")(type "KiCad")' - f'(uri "${{KIPRJMOD}}/OpAmps.kicad_sym")(options "")(descr ""))\n' + '(uri "${KIPRJMOD}/OpAmps.kicad_sym")(options "")(descr ""))\n' ")\n" ) (tmp_path / "sym-lib-table").write_text(table_content) @@ -368,6 +367,17 @@ class TestGetKicadSymbolDir: assert os.path.isfile(os.path.join(sym_dir, "Device.kicad_sym")) def test_env_override(self, tmp_path, monkeypatch): + # Clear any ambient symbol-dir vars so the override under test is the + # only candidate (a real KICAD_SYMBOL_DIR or install path would + # otherwise take precedence). + for var in ( + "KICAD_SYMBOL_DIR", + "KICAD10_SYMBOL_DIR", + "KICAD9_SYMBOL_DIR", + "KICAD8_SYMBOL_DIR", + "KICAD7_SYMBOL_DIR", + ): + monkeypatch.delenv(var, raising=False) monkeypatch.setenv("KICAD9_SYMBOL_DIR", str(tmp_path)) sym_dir = get_kicad_symbol_dir() assert sym_dir == str(tmp_path)