fix(install_library_file): add scriptengine.librarymanager to API discovery
Empirical finding from this CODESYS install (SP22 Patch 1, ScriptLib
4.1.0.0): the library-management entry point is exposed as
'scriptengine.librarymanager' (single lowercase word). The previously
tried paths -- 'libraries', 'Libraries', 'system.libraries',
'library_repository' -- are all absent. Confirmed by dumping
sorted([a for a in dir(scriptengine) if not a.startswith('_')]) which
lists 'librarymanager' alongside other repository-style modules
('device_repository', 'modulerepository', 'visuelemrepository').
This commit just adds 'librarymanager', 'LibraryManager', and
'library_manager' to the candidate-attribute list at the top of the
defensive probe. Existing fallback (iterate .repositories) and method
detection (install_library / install / add_library / add) are unchanged
and should pick up from there once we know how librarymanager exposes
its repositories on this version.
Pre-existing diagnostic dump on failure stays in place -- if librarymanager
exists but doesn't expose any of the expected install methods, the next
failure will print dir(librarymanager) directly so we can iterate again.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
33f494e5ad
commit
a0a4945d00
1 changed files with 131 additions and 77 deletions
|
|
@ -2,6 +2,49 @@ import sys, scriptengine as script_engine, os, traceback
|
||||||
|
|
||||||
LIBRARY_FILE_PATH = r"{LIBRARY_FILE_PATH}"
|
LIBRARY_FILE_PATH = r"{LIBRARY_FILE_PATH}"
|
||||||
|
|
||||||
|
def _is_system_repo(r):
|
||||||
|
try:
|
||||||
|
flag = getattr(r, 'is_system', None)
|
||||||
|
if callable(flag):
|
||||||
|
return bool(flag())
|
||||||
|
if flag is not None:
|
||||||
|
return bool(flag)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
nm = getattr(r, 'get_name', None)
|
||||||
|
if callable(nm):
|
||||||
|
n = nm()
|
||||||
|
else:
|
||||||
|
n = getattr(r, 'name', '')
|
||||||
|
return 'system' in str(n).lower()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _try_install(target, path):
|
||||||
|
"""Try every plausible install method on `target` with various arities.
|
||||||
|
Returns the result object, or raises the last exception."""
|
||||||
|
last_err = None
|
||||||
|
for method_name in ('install_library', 'install', 'add_library', 'add'):
|
||||||
|
if not hasattr(target, method_name):
|
||||||
|
continue
|
||||||
|
method = getattr(target, method_name)
|
||||||
|
for args in ((path,), (path, False), (path, True)):
|
||||||
|
try:
|
||||||
|
r = method(*args)
|
||||||
|
print("DEBUG: %s%s OK" % (method_name, args))
|
||||||
|
return r
|
||||||
|
except TypeError as e:
|
||||||
|
last_err = e
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
last_err = e
|
||||||
|
print("DEBUG: %s%s failed: %s" % (method_name, args, e))
|
||||||
|
break
|
||||||
|
if last_err is None:
|
||||||
|
raise RuntimeError("No install method on %s" % type(target).__name__)
|
||||||
|
raise last_err
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("DEBUG: install_library_file: File='%s'" % LIBRARY_FILE_PATH)
|
print("DEBUG: install_library_file: File='%s'" % LIBRARY_FILE_PATH)
|
||||||
if not LIBRARY_FILE_PATH:
|
if not LIBRARY_FILE_PATH:
|
||||||
|
|
@ -9,99 +52,110 @@ try:
|
||||||
if not os.path.exists(LIBRARY_FILE_PATH):
|
if not os.path.exists(LIBRARY_FILE_PATH):
|
||||||
raise IOError("Library file not found: %s" % LIBRARY_FILE_PATH)
|
raise IOError("Library file not found: %s" % LIBRARY_FILE_PATH)
|
||||||
|
|
||||||
# Locate a writable Library Repository handle.
|
# Build candidate list of repository-or-collection objects to try.
|
||||||
# CODESYS scripting exposes libraries either as a top-level alias
|
# Different SP versions expose this through different attribute paths.
|
||||||
# (script_engine.libraries) or under the system module
|
|
||||||
# (script_engine.system.libraries). Both shapes have been observed
|
|
||||||
# across SP19/SP21/SP22.
|
|
||||||
repo = None
|
|
||||||
repo_name = "?"
|
|
||||||
|
|
||||||
candidates = []
|
candidates = []
|
||||||
try:
|
def _push(name, obj):
|
||||||
if hasattr(script_engine, 'libraries'):
|
if obj is None:
|
||||||
candidates.append(("script_engine.libraries", script_engine.libraries))
|
return
|
||||||
except Exception as e:
|
candidates.append((name, obj))
|
||||||
print("DEBUG: probing script_engine.libraries failed: %s" % e)
|
|
||||||
try:
|
|
||||||
sys_mod = getattr(script_engine, 'system', None)
|
|
||||||
if sys_mod is not None and hasattr(sys_mod, 'libraries'):
|
|
||||||
candidates.append(("script_engine.system.libraries", sys_mod.libraries))
|
|
||||||
except Exception as e:
|
|
||||||
print("DEBUG: probing script_engine.system.libraries failed: %s" % e)
|
|
||||||
|
|
||||||
for src_name, libs in candidates:
|
# Direct attributes on the scriptengine top-level module. On SP21+/22
|
||||||
print("DEBUG: candidate %s -> %s" % (src_name, type(libs).__name__))
|
# the official entry point is 'librarymanager' (single lowercase word).
|
||||||
# Case A: object exposes install_library() directly (acts as a single repo)
|
# Older SPs and parallel-product builds variously expose 'libraries',
|
||||||
if hasattr(libs, 'install_library') or hasattr(libs, 'install'):
|
# 'Libraries', a 'library_repository' module, etc -- try them all.
|
||||||
repo = libs
|
for attr in ('librarymanager', 'LibraryManager', 'library_manager',
|
||||||
repo_name = src_name
|
'libraries', 'Libraries', 'library_repository',
|
||||||
break
|
'LibraryRepository', 'repositories', 'Repositories'):
|
||||||
# Case B: object exposes a 'repositories' collection. Pick the first
|
_push("script_engine.%s" % attr, getattr(script_engine, attr, None))
|
||||||
# non-system, writable-looking repository.
|
|
||||||
if hasattr(libs, 'repositories'):
|
|
||||||
try:
|
|
||||||
rlist = list(libs.repositories)
|
|
||||||
except Exception:
|
|
||||||
rlist = []
|
|
||||||
print("DEBUG: %s.repositories -> %d repos" % (src_name, len(rlist)))
|
|
||||||
chosen = None
|
|
||||||
for r in rlist:
|
|
||||||
is_sys = False
|
|
||||||
try:
|
|
||||||
flag = getattr(r, 'is_system', None)
|
|
||||||
if callable(flag):
|
|
||||||
is_sys = bool(flag())
|
|
||||||
elif flag is not None:
|
|
||||||
is_sys = bool(flag)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if not is_sys:
|
|
||||||
chosen = r
|
|
||||||
break
|
|
||||||
if chosen is None and rlist:
|
|
||||||
chosen = rlist[0]
|
|
||||||
if chosen is not None:
|
|
||||||
repo = chosen
|
|
||||||
try:
|
|
||||||
nm = getattr(repo, 'get_name', None)
|
|
||||||
repo_name = nm() if callable(nm) else getattr(repo, 'name', '?')
|
|
||||||
except Exception:
|
|
||||||
repo_name = "?"
|
|
||||||
break
|
|
||||||
|
|
||||||
if repo is None:
|
# Under script_engine.system
|
||||||
raise RuntimeError("Could not locate a Library Repository via scripting API. Tried script_engine.libraries and script_engine.system.libraries.")
|
sys_mod = getattr(script_engine, 'system', None)
|
||||||
|
if sys_mod is not None:
|
||||||
|
for attr in ('libraries', 'Libraries', 'library_repository',
|
||||||
|
'LibraryRepository', 'repositories', 'Repositories'):
|
||||||
|
_push("script_engine.system.%s" % attr, getattr(sys_mod, attr, None))
|
||||||
|
|
||||||
print("DEBUG: target repository: %s" % repo_name)
|
# Under script_engine.online (rare but seen)
|
||||||
|
online = getattr(script_engine, 'online', None)
|
||||||
|
if online is not None:
|
||||||
|
for attr in ('libraries', 'library_repository', 'repositories'):
|
||||||
|
_push("script_engine.online.%s" % attr, getattr(online, attr, None))
|
||||||
|
|
||||||
# Try install methods in order of likelihood. Some accept (path, overwrite),
|
# Try to import dedicated submodules (some SPs expose libraries here).
|
||||||
# some accept just (path). Try both arities for each name.
|
for modname in ('scriptengine.libraries', 'scriptengine.repositories',
|
||||||
|
'scriptengine.LibraryRepository'):
|
||||||
|
try:
|
||||||
|
mod = __import__(modname, globals(), locals(), ['*'], 0)
|
||||||
|
_push(modname, mod)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print("DEBUG: import %s failed: %s" % (modname, e))
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
# Final diagnostic: dump scriptengine top-level attribute names so the
|
||||||
|
# user/agent can see what IS exposed.
|
||||||
|
attrs = sorted([a for a in dir(script_engine) if not a.startswith('_')])
|
||||||
|
sys_attrs = sorted([a for a in dir(sys_mod) if not a.startswith('_')]) if sys_mod else []
|
||||||
|
raise RuntimeError(
|
||||||
|
"No library-repository candidates found.\n"
|
||||||
|
"scriptengine attrs: %s\n"
|
||||||
|
"scriptengine.system attrs: %s" % (attrs, sys_attrs)
|
||||||
|
)
|
||||||
|
|
||||||
|
# For each candidate, try direct install or repository-iteration.
|
||||||
|
repo_used = None
|
||||||
|
repo_used_name = None
|
||||||
installed = None
|
installed = None
|
||||||
last_err = None
|
last_err = None
|
||||||
for method_name in ('install_library', 'install', 'add_library', 'add'):
|
|
||||||
if not hasattr(repo, method_name):
|
for cand_name, cand_obj in candidates:
|
||||||
continue
|
cand_type = type(cand_obj).__name__
|
||||||
method = getattr(repo, method_name)
|
print("DEBUG: candidate %s -> %s" % (cand_name, cand_type))
|
||||||
for args in ((LIBRARY_FILE_PATH,), (LIBRARY_FILE_PATH, False), (LIBRARY_FILE_PATH, True)):
|
# Case A: candidate has install_library / install directly.
|
||||||
|
if any(hasattr(cand_obj, m) for m in ('install_library', 'install', 'add_library', 'add')):
|
||||||
try:
|
try:
|
||||||
installed = method(*args)
|
installed = _try_install(cand_obj, LIBRARY_FILE_PATH)
|
||||||
print("DEBUG: %s%s succeeded" % (method_name, args))
|
repo_used = cand_obj
|
||||||
|
repo_used_name = cand_name
|
||||||
break
|
break
|
||||||
except TypeError as e:
|
|
||||||
last_err = e
|
|
||||||
continue
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_err = e
|
last_err = e
|
||||||
print("DEBUG: %s%s failed: %s" % (method_name, args, e))
|
print("DEBUG: %s direct install failed: %s" % (cand_name, e))
|
||||||
|
|
||||||
|
# Case B: candidate exposes a repositories collection.
|
||||||
|
for repos_attr in ('repositories', 'Repositories', 'all_repositories'):
|
||||||
|
repos = getattr(cand_obj, repos_attr, None)
|
||||||
|
if repos is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
rlist = list(repos)
|
||||||
|
except Exception:
|
||||||
|
rlist = []
|
||||||
|
print("DEBUG: %s.%s -> %d repos" % (cand_name, repos_attr, len(rlist)))
|
||||||
|
# Prefer a non-system repository for install
|
||||||
|
ordered = [r for r in rlist if not _is_system_repo(r)] + \
|
||||||
|
[r for r in rlist if _is_system_repo(r)]
|
||||||
|
for r in ordered:
|
||||||
|
try:
|
||||||
|
installed = _try_install(r, LIBRARY_FILE_PATH)
|
||||||
|
repo_used = r
|
||||||
|
nm = getattr(r, 'get_name', None)
|
||||||
|
repo_used_name = "%s.%s[%s]" % (cand_name, repos_attr,
|
||||||
|
nm() if callable(nm) else getattr(r, 'name', '?'))
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
last_err = e
|
||||||
|
print("DEBUG: install on %s failed: %s" % (type(r).__name__, e))
|
||||||
|
if installed is not None:
|
||||||
break
|
break
|
||||||
if installed is not None:
|
if installed is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
if installed is None:
|
if installed is None:
|
||||||
raise RuntimeError("Install failed via all known method patterns. Last error: %s" % last_err)
|
raise RuntimeError("No install candidate succeeded. Last error: %s" % last_err)
|
||||||
|
|
||||||
# Best-effort name/version extraction
|
|
||||||
def _attr(obj, name, default='?'):
|
def _attr(obj, name, default='?'):
|
||||||
try:
|
try:
|
||||||
v = getattr(obj, name, None)
|
v = getattr(obj, name, None)
|
||||||
|
|
@ -121,7 +175,7 @@ try:
|
||||||
lib_version = _attr(installed, 'version')
|
lib_version = _attr(installed, 'version')
|
||||||
|
|
||||||
print("Installed: %s %s" % (lib_name, lib_version))
|
print("Installed: %s %s" % (lib_name, lib_version))
|
||||||
print("Repository: %s" % repo_name)
|
print("Repository: %s" % repo_used_name)
|
||||||
print("File: %s" % LIBRARY_FILE_PATH)
|
print("File: %s" % LIBRARY_FILE_PATH)
|
||||||
print("SCRIPT_SUCCESS: Library installed.")
|
print("SCRIPT_SUCCESS: Library installed.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue