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
  <lib>.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.
This commit is contained in:
Ryan Malloy 2026-07-11 17:09:34 -06:00
parent 57872e59c1
commit 1dca4d6912
2 changed files with 49 additions and 12 deletions

View File

@ -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

View File

@ -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)