From 95e23c70ff20084b4ae8091dba0bd15398e56a5d Mon Sep 17 00:00:00 2001 From: gsdali <51393997+gsdali@users.noreply.github.com> Date: Tue, 19 May 2026 13:19:50 +1000 Subject: [PATCH] fix: resolve `${KICAD_3RD_PARTY}` (no version prefix) in lib URI resolvers (#187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Import-LIB-KiCad-Plugin documentation registers third-party libraries using `${KICAD_3RD_PARTY}` (without a KiCad-version prefix). KiCad accepts both unprefixed and version-prefixed forms in lib-tables, and the analogous `KICAD_SYMBOL_DIR` was already handled in `library_symbol.py`'s env-var dictionary. `KICAD_3RD_PARTY` was missing from both `_resolve_uri` env-var dictionaries (in `python/commands/library.py` and `python/commands/library_symbol.py`), so lib-table rows authored as `${KICAD_3RD_PARTY}/Foo.kicad_sym` or `${KICAD_3RD_PARTY}/Foo.pretty` failed to substitute, were treated as non-existent paths, and disappeared from `list_symbol_libraries` / `list_libraries` results — even though KiCad's GUI showed them correctly. Changes: - Add `KICAD_3RD_PARTY` to both `_resolve_uri` env-var dictionaries. - Add `KICAD_3RD_PARTY` fallback in `SymbolLibraryManager._find_3rd_party_dir`. - Refactor `LibraryManager._find_kicad_3rdparty_dir` to check all four env-var forms (KICAD10/9/8_3RD_PARTY + KICAD_3RD_PARTY) consistently. - Add regression tests in `tests/test_kicad_3rd_party_env_resolution.py`. Reproduces with: - Set `KICAD_3RD_PARTY` env var in the MCP server's environment. - Register `(lib (name "Foo") (type "KiCad") (uri "\${KICAD_3RD_PARTY}/Foo.kicad_sym") ...)` in the global sym-lib-table. - Place a real `Foo.kicad_sym` at the resolved path. - Before: `list_symbol_libraries` does not return `Foo`. - After: `Foo` is listed. --- python/commands/library.py | 10 +-- python/commands/library_symbol.py | 3 + tests/test_kicad_3rd_party_env_resolution.py | 75 ++++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 tests/test_kicad_3rd_party_env_resolution.py diff --git a/python/commands/library.py b/python/commands/library.py index 353d3ff..eebd185 100644 --- a/python/commands/library.py +++ b/python/commands/library.py @@ -145,6 +145,7 @@ class LibraryManager: "KICAD10_3RD_PARTY": self._find_kicad_3rdparty_dir(), "KICAD9_3RD_PARTY": self._find_kicad_3rdparty_dir(), "KICAD8_3RD_PARTY": self._find_kicad_3rdparty_dir(), + "KICAD_3RD_PARTY": self._find_kicad_3rdparty_dir(), } # Project directory @@ -205,10 +206,11 @@ class LibraryManager: import json # 1. Check shell environment variable first - if "KICAD9_3RD_PARTY" in os.environ: - path = os.environ["KICAD9_3RD_PARTY"] - if os.path.isdir(path): - return path + for var in ("KICAD10_3RD_PARTY", "KICAD9_3RD_PARTY", "KICAD8_3RD_PARTY", "KICAD_3RD_PARTY"): + if var in os.environ: + path = os.environ[var] + if os.path.isdir(path): + return path # 2. Check kicad_common.json for user-defined variables kicad_common_paths = [ diff --git a/python/commands/library_symbol.py b/python/commands/library_symbol.py index 1f30de3..9c7630b 100644 --- a/python/commands/library_symbol.py +++ b/python/commands/library_symbol.py @@ -164,6 +164,7 @@ class SymbolLibraryManager: "KICAD10_3RD_PARTY": self._find_3rd_party_dir(), "KICAD9_3RD_PARTY": self._find_3rd_party_dir(), "KICAD8_3RD_PARTY": self._find_3rd_party_dir(), + "KICAD_3RD_PARTY": self._find_3rd_party_dir(), "KISYSSYM": self._find_kicad_symbol_dir(), } @@ -227,6 +228,8 @@ class SymbolLibraryManager: possible_paths.insert(0, os.environ["KICAD9_3RD_PARTY"]) if "KICAD8_3RD_PARTY" in os.environ: possible_paths.insert(0, os.environ["KICAD8_3RD_PARTY"]) + if "KICAD_3RD_PARTY" in os.environ: + possible_paths.insert(0, os.environ["KICAD_3RD_PARTY"]) for path in possible_paths: if os.path.isdir(path): diff --git a/tests/test_kicad_3rd_party_env_resolution.py b/tests/test_kicad_3rd_party_env_resolution.py new file mode 100644 index 0000000..ac26f05 --- /dev/null +++ b/tests/test_kicad_3rd_party_env_resolution.py @@ -0,0 +1,75 @@ +""" +Regression tests for ``${KICAD_3RD_PARTY}`` (no-version-prefix) URI resolution +in both the symbol and footprint library managers. + +Background: + The Import-LIB-KiCad-Plugin (impart) documentation registers third-party + libraries using ``${KICAD_3RD_PARTY}`` without a KiCad-version prefix. + KiCad accepts both unprefixed and version-prefixed forms in lib-tables. + + Prior to this fix the env-var dictionaries in ``_resolve_uri`` only handled + ``KICAD8/9/10_3RD_PARTY`` (analogous to ``KICAD_SYMBOL_DIR`` which *was* + in the dictionary without a prefix). Lib-table rows authored as + ``${KICAD_3RD_PARTY}/Foo.kicad_sym`` or ``${KICAD_3RD_PARTY}/Foo.pretty`` + therefore failed to resolve and disappeared from MCP listings even though + KiCad's GUI showed them correctly. +""" + +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).parent.parent / "python")) + +from commands.library import LibraryManager +from commands.library_symbol import SymbolLibraryManager + + +@pytest.mark.unit +def test_symbol_manager_resolves_unprefixed_kicad_3rd_party(monkeypatch, tmp_path): + monkeypatch.setenv("KICAD_3RD_PARTY", str(tmp_path)) + target = tmp_path / "EasyEDA.kicad_sym" + target.touch() + + manager = SymbolLibraryManager.__new__(SymbolLibraryManager) + manager.project_path = None + + resolved = manager._resolve_uri("${KICAD_3RD_PARTY}/EasyEDA.kicad_sym") + assert resolved == str(target) + + +@pytest.mark.unit +def test_library_manager_resolves_unprefixed_kicad_3rd_party(monkeypatch, tmp_path): + monkeypatch.setenv("KICAD_3RD_PARTY", str(tmp_path)) + target_dir = tmp_path / "EasyEDA.pretty" + target_dir.mkdir() + + manager = LibraryManager.__new__(LibraryManager) + manager.project_path = None + + resolved = manager._resolve_uri("${KICAD_3RD_PARTY}/EasyEDA.pretty") + assert resolved == str(target_dir) + + +@pytest.mark.unit +def test_versioned_and_unprefixed_forms_both_work(monkeypatch, tmp_path): + """Both ``${KICAD10_3RD_PARTY}`` and ``${KICAD_3RD_PARTY}`` must resolve when + the corresponding env vars are set, even pointing at the same directory.""" + monkeypatch.setenv("KICAD10_3RD_PARTY", str(tmp_path)) + monkeypatch.setenv("KICAD_3RD_PARTY", str(tmp_path)) + + sym_target = tmp_path / "Mixed.kicad_sym" + sym_target.touch() + fp_target = tmp_path / "Mixed.pretty" + fp_target.mkdir() + + sym_mgr = SymbolLibraryManager.__new__(SymbolLibraryManager) + sym_mgr.project_path = None + fp_mgr = LibraryManager.__new__(LibraryManager) + fp_mgr.project_path = None + + assert sym_mgr._resolve_uri("${KICAD10_3RD_PARTY}/Mixed.kicad_sym") == str(sym_target) + assert sym_mgr._resolve_uri("${KICAD_3RD_PARTY}/Mixed.kicad_sym") == str(sym_target) + assert fp_mgr._resolve_uri("${KICAD10_3RD_PARTY}/Mixed.pretty") == str(fp_target) + assert fp_mgr._resolve_uri("${KICAD_3RD_PARTY}/Mixed.pretty") == str(fp_target)