From d4e71f61cafaf34f0bc983b2269ac4420191171f Mon Sep 17 00:00:00 2001 From: Karstein Phobic Nyvold Kvistad Date: Wed, 29 Apr 2026 07:39:31 +0200 Subject: [PATCH] fix(compile_messages): discover real category GUIDs via system.get_message_categories() The 0.6.3 fix replaced 'first-non-empty-pattern-wins' with category iteration but used a HARDCODED list of 'well-known V3.5 GUIDs' that was wrong. None of those GUIDs matched the actual Build category, so compile errors stayed invisible (compile_project still reported '0 errors' even when the IDE-side download path saw them). Diagnosed via a one-shot probe injected into compile_project on 2026-04-29: dumped attrs of script_engine.system, then called script_engine.system.get_message_categories() (the METHOD) directly. That returned the actual 7 category GUIDs in this CODESYS V3.5 SP22 Patch 1 install: 05581bd1-66d3-4251-aff2-047cc8e9adf7 Offline Help 936e1a33-3af8-47fa-b40b-903f0ae0b6cc Application Composer a9b26e07-6ae1-4c06-9cd1-9ddddd397a2d SVN 0a6fcb64-7f24-43c6-a6d3-f70cb5d31114 (no parameterless ctor; Git) 194b48a9-ab51-43ae-b9a9-51d3edaaddf3 Script Messages 97f48d64-a2a3-4856-b640-75c046e37ea9 Build <-- the one we needed 220493a1-f49b-4416-9a3f-a545db707cbe Additional code checks Real fix: replace the hardcoded list in _enumerate_categories() in both compile_project.py and get_compile_messages.py with a call to system.get_message_categories(); label each one via get_message_category_description(guid). Iterate per category as before. GUIDs are now discovered at runtime so the same code works on any SP and any locale. Verification: injected `THIS_IS_NOT_VALID_IEC_KEYWORD;` into PLC_PRG.implementation, ran compile_project; output now reads "1 error(s), 2 warning(s). ERROR: Identifier 'THIS_IS_NOT_VALID_IEC_KEYWORD' not defined". Restored PLC_PRG; 0 error(s) again. End-to-end fix confirmed. Bumped 0.6.3 -> 0.6.4. --- package.json | 2 +- src/scripts/compile_project.py | 63 ++++++++++++++++------------- src/scripts/get_compile_messages.py | 58 +++++++++++++------------- 3 files changed, 63 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index 8e93d25..9bc71a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codesys-mcp-sp21-plus", - "version": "0.6.3", + "version": "0.6.4", "description": "Codesys-MCP-SP21+ -- fork of luke-harriman/Codesys-MCP carrying CODESYS V3.5 SP22 Patch 1 fixes (and forward-compat with later SPs): script-engine API drift, online/runtime tool auto-login, dual-SHA release classifier, set_pou_code omitted-decl wipe fix, add_library managed-overload, etc. MCP server for CODESYS with persistent UI instance and file-based IPC.", "main": "dist/server.js", "bin": { diff --git a/src/scripts/compile_project.py b/src/scripts/compile_project.py index bf115cb..65c504a 100644 --- a/src/scripts/compile_project.py +++ b/src/scripts/compile_project.py @@ -106,36 +106,43 @@ def _build_message_entry(msg, category_name=None): def _enumerate_categories(script_engine_arg): + """Returns a list of (label, category_guid_or_None) tuples for every + message category the IDE exposes plus a (None, None) sentinel for the + no-filter call. + + Discovery path: script_engine.system.get_message_categories() (the + METHOD) returns the live List[Guid] of category IDs registered in + this IDE session. script_engine.system.get_message_category_description(guid) + gives the human-readable label per category ('Build', 'Application + Composer', 'SVN', 'Script Messages', 'Additional code checks', etc.). + + History: pre-0.6.4 the function looked at `system.message_categories` + (a property) which doesn't exist, then fell back to a hardcoded list + of GUIDs that happened to NOT include the actual Build category + (97f48d64-a2a3-4856-b640-75c046e37ea9). Result: compile errors were + invisible. Verified by diagnostic probe with intentional syntax-error + injection on 2026-04-29.""" cats = [('', None)] + se_sys = getattr(script_engine_arg, 'system', None) + if se_sys is None or not hasattr(se_sys, 'get_message_categories'): + return cats try: - se_sys = getattr(script_engine_arg, 'system', None) - if se_sys is not None: - mc = getattr(se_sys, 'message_categories', None) - if mc is not None: - try: - for c in mc: - try: - label = ( - _coerce_str(getattr(c, 'name', None)) - or _coerce_str(getattr(c, 'guid', None)) - or _coerce_str(c) - or '' - ) - cats.append((label, c)) - except Exception: - continue - except Exception: - pass - except Exception: - pass - well_known = [ - ('Compile (well-known GUID)', '90F1B997-7AB7-4B11-B637-D55D71BC4F2A'), - ('Build (well-known GUID)', '7390398F-1B2F-4B30-B6E2-37F2BB7B57E0'), - ('Online (well-known GUID)', '15F65557-DC73-4193-B7F2-EFF5A2A6C10C'), - ('LibMan (well-known GUID)', '0B8D54FB-C68A-43F9-9B4D-79DBE1F8DF44'), - ] - for lbl, g in well_known: - cats.append((lbl, g)) + guids = se_sys.get_message_categories() + except Exception as e: + print("DEBUG: get_message_categories() raised: %s" % e) + return cats + if guids is None: + return cats + for g in guids: + label = None + try: + if hasattr(se_sys, 'get_message_category_description'): + label = _coerce_str(se_sys.get_message_category_description(g)) + except Exception: + label = None + if not label: + label = _coerce_str(g) or '' + cats.append((label, g)) return cats diff --git a/src/scripts/get_compile_messages.py b/src/scripts/get_compile_messages.py index 0835b4f..e40f2e6 100644 --- a/src/scripts/get_compile_messages.py +++ b/src/scripts/get_compile_messages.py @@ -106,39 +106,35 @@ def _build_message_entry(msg, category_name=None): def _enumerate_categories(script_engine_arg): - """Returns a list of (label, category_obj_or_guid_or_None). Always - includes a (None, None) sentinel for the no-filter call.""" + """Returns a list of (label, category_guid_or_None) tuples for every + message category the IDE exposes plus a (None, None) sentinel for the + no-filter call. + + Discovery path: script_engine.system.get_message_categories() (the + METHOD) returns the live List[Guid]. script_engine.system.get_message_category_description(guid) + gives the human-readable label per category. See compile_project.py + for the verified-on-SP22 history note.""" cats = [('', None)] + se_sys = getattr(script_engine_arg, 'system', None) + if se_sys is None or not hasattr(se_sys, 'get_message_categories'): + return cats try: - se_sys = getattr(script_engine_arg, 'system', None) - if se_sys is not None: - mc = getattr(se_sys, 'message_categories', None) - if mc is not None: - try: - for c in mc: - try: - label = ( - _coerce_str(getattr(c, 'name', None)) - or _coerce_str(getattr(c, 'guid', None)) - or _coerce_str(c) - or '' - ) - cats.append((label, c)) - except Exception: - continue - except Exception: - pass - except Exception: - pass - # Well-known V3.5 category GUIDs (probed as fallback strings). - well_known = [ - ('Compile (well-known GUID)', '90F1B997-7AB7-4B11-B637-D55D71BC4F2A'), - ('Build (well-known GUID)', '7390398F-1B2F-4B30-B6E2-37F2BB7B57E0'), - ('Online (well-known GUID)', '15F65557-DC73-4193-B7F2-EFF5A2A6C10C'), - ('LibMan (well-known GUID)', '0B8D54FB-C68A-43F9-9B4D-79DBE1F8DF44'), - ] - for lbl, g in well_known: - cats.append((lbl, g)) + guids = se_sys.get_message_categories() + except Exception as e: + print("DEBUG: get_message_categories() raised: %s" % e) + return cats + if guids is None: + return cats + for g in guids: + label = None + try: + if hasattr(se_sys, 'get_message_category_description'): + label = _coerce_str(se_sys.get_message_category_description(g)) + except Exception: + label = None + if not label: + label = _coerce_str(g) or '' + cats.append((label, g)) return cats