Where
python/MDSplus/version.py, load_library():
if isdarwin and not os.getenv('DYLD_LIBRARY_PATH'):
if os.getenv('MDSPLUS_LIBRARY_PATH'):
os.environ['DYLD_LIBRARY_PATH'] = os.getenv('MDSPLUS_LIBRARY_PATH')
elif os.getenv('MDSPLUS_DIR'):
os.environ['DYLD_LIBRARY_PATH'] = os.path.join(os.getenv('MDSPLUS_DIR'), 'lib')
else:
os.environ['DYLD_LIBRARY_PATH'] = '/usr/local/mdsplus/lib'
...
if isdarwin:
for path in os.environ.get('DYLD_LIBRARY_PATH', '').split(':'):
try:
return C.CDLL('%s/lib%s.dylib' % (path, name))
except:
continue
return C.CDLL('lib%s.dylib' % name)
The problem
On macOS the code sets os.environ['DYLD_LIBRARY_PATH'] after the process has started. macOS does not honor a late-set DYLD_LIBRARY_PATH for dlopen (SIP strips DYLD_* from protected interpreters; the dynamic loader reads it at launch). So the assignment looks like it is steering the loader but cannot.
Why it isn't visibly breaking today
The darwin branch does not rely on the loader honoring the variable — it reads the string back and builds the absolute "<path>/lib<name>.dylib" itself, then CDLLs the full path. The env var is effectively just a scratch variable carrying the search directory a few lines down. And the MDSplus C libraries locate their own dependencies via MDSPLUS_LIBRARY_PATH (explicit lookup in the C code), so nothing downstream needs DYLD_LIBRARY_PATH either.
Why it should still be fixed
- Environment mutation leaks. Setting
os.environ['DYLD_LIBRARY_PATH'] persists for the whole process and is inherited by any subprocess spawned afterwards, perturbing unrelated tools.
- The
not os.getenv('DYLD_LIBRARY_PATH') guard is fragile. If DYLD_LIBRARY_PATH is already set (to anything that does not contain the MDSplus lib dir), the setup block is skipped entirely: the loop iterates the pre-existing dirs, the explicit-path CDLLs fail, and it falls back to a bare lib<name>.dylib, which may not find the libraries. In that case MDSPLUS_LIBRARY_PATH is also ignored.
Suggested direction
Do not touch os.environ at all. Compute a local search-path list (MDSPLUS_LIBRARY_PATH -> $MDSPLUS_DIR/lib -> /usr/local/mdsplus/lib), unconditionally (not gated on DYLD_LIBRARY_PATH being unset), and build the explicit dylib paths from that local list. This removes the subprocess leak and the fragile guard while keeping the current explicit-absolute-path loading behavior.
Filed as a follow-up spun off from the python "local wheel" work; not a regression.
Where
python/MDSplus/version.py,load_library():The problem
On macOS the code sets
os.environ['DYLD_LIBRARY_PATH']after the process has started. macOS does not honor a late-setDYLD_LIBRARY_PATHfordlopen(SIP stripsDYLD_*from protected interpreters; the dynamic loader reads it at launch). So the assignment looks like it is steering the loader but cannot.Why it isn't visibly breaking today
The darwin branch does not rely on the loader honoring the variable — it reads the string back and builds the absolute
"<path>/lib<name>.dylib"itself, thenCDLLs the full path. The env var is effectively just a scratch variable carrying the search directory a few lines down. And the MDSplus C libraries locate their own dependencies viaMDSPLUS_LIBRARY_PATH(explicit lookup in the C code), so nothing downstream needsDYLD_LIBRARY_PATHeither.Why it should still be fixed
os.environ['DYLD_LIBRARY_PATH']persists for the whole process and is inherited by any subprocess spawned afterwards, perturbing unrelated tools.not os.getenv('DYLD_LIBRARY_PATH')guard is fragile. IfDYLD_LIBRARY_PATHis already set (to anything that does not contain the MDSpluslibdir), the setup block is skipped entirely: the loop iterates the pre-existing dirs, the explicit-pathCDLLs fail, and it falls back to a barelib<name>.dylib, which may not find the libraries. In that caseMDSPLUS_LIBRARY_PATHis also ignored.Suggested direction
Do not touch
os.environat all. Compute a local search-path list (MDSPLUS_LIBRARY_PATH->$MDSPLUS_DIR/lib->/usr/local/mdsplus/lib), unconditionally (not gated onDYLD_LIBRARY_PATHbeing unset), and build the explicit dylib paths from that local list. This removes the subprocess leak and the fragile guard while keeping the current explicit-absolute-path loading behavior.Filed as a follow-up spun off from the python "local wheel" work; not a regression.