Repository navigation
Double-click end-user installer (~/.ndi) and MATLAB-safe viewer console scripts - #332
Merged
Merged
Conversation
install/: install-ndi.sh (Linux, macOS), install-ndi.ps1 (Windows), the
double-clickable "Install NDI.command" / "Install NDI.cmd", and a README for
end users. A pinned uv (0.11.32) installs Python 3.12 and
ndi[gui,napari,pyraview] as a uv tool under ~/.ndi/{bin,python,venvs} only,
with Pyraview from its release wheels (--no-build-package pyraview).
--uninstall removes exactly those folders and the marked PATH block.
NDI_INSTALL_SOURCE / NDI_PYRAVIEW_* / NDI_INSTALL_PYTHON let CI install the
code under test.
napariViewGEF and napariViewLightsheet now go through src/_ndi_launch.py, a
stdlib-only module beside the package: started from MATLAB it drops
LD_LIBRARY_PATH, LD_PRELOAD, DYLD_*, PYTHONHOME and PYTHONPATH and re-execs
BEFORE `import ndi` loads numpy (cleans PATH in place on Windows). Replaces
the shell wrappers tools/napariViewGEF and scripts/napariViewLightsheet.
GUI.GEFManager.ViewerLauncher now defaults to "" (find it automatically).
.github/workflows/installer.yml runs the real installer with git and
compilers hidden on Linux x86_64/aarch64, Windows, macOS arm64, macOS Intel
and x86_64-under-Rosetta, then checks the commands, the MATLAB re-exec, the
PATH block and a clean uninstall.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KyLF4xAq4Sm4aT5gDyMTS
The macOS runners' ~/.bash_profile ends without a newline. Adding the PATH block added one, and --uninstall left it behind. The block's first line now records when a newline was added (and when the file was created), and --uninstall takes exactly that away again. A pre-existing empty profile file is no longer deleted; only one this script created. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KyLF4xAq4Sm4aT5gDyMTS
…ninstall The Windows runner's user Path ends in ';'. Dropping empty entries on the way in meant uninstall wrote back the string without it. Entries are now kept as they are, ours goes before a trailing empty one, and removal takes only ours, so add-then-remove returns the identical string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KyLF4xAq4Sm4aT5gDyMTS
Resolves the CHANGELOG conflict with #333 (both sides added an entry under Unreleased/Added). Also puts this PR's entries in the right sections: the first commit had opened a second 'Changed' heading above existing 'Added' entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KyLF4xAq4Sm4aT5gDyMTS
Contributor
Author
|
MATLAB/Python bridge completeness is red on 14f404a, and the cause is not this PR.
The fix is #334, which ports #1033 and bumps exactly these 10 hashes; its bridge job is green.
Plan: merge #334 first. I will then merge main into this branch and resolve the expected overlap: both PRs touch Generated by Claude Code |
CHANGELOG conflict only: both sides added Unreleased entries; kept all of main's and this PR's two. preferences.py and the preferences bridge entry merged cleanly (#334's batch-size default and 7180cc8 hash, this PR's ViewerLauncher default and decision_log note). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KyLF4xAq4Sm4aT5gDyMTS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An installer for end users on Linux, macOS (Apple Silicon and Intel) and Windows that NDI-matlab can also run, and console scripts that are safe to start from MATLAB without shell wrappers. The
ndi_install.pyfile is untouched. A freshndi_install.pyinstall behaves as before, except the two viewers now clean up after MATLAB themselves (Part 1).The NDI-matlab-facing contract is implemented as specified. That means
~/.ndi/{bin,python,venvs}and nothing else,~/.ndi/bin/{ndi,napariViewGEF,napariViewLightsheet}(.exeon Windows) plusuv, the script URLs, the flags--yes/--no-path/--uninstalland-Yes/-NoPath/-Uninstall, exit 0 only when the check passes, a last line ofNDI installed: <abs path of ~/.ndi/bin>, andNDI_FROM_MATLAB=1. Three internal details differ from the brief:src/_ndi_launch.py, notndi/common/launch.py. The console scripts now point at_ndi_launch:napariViewGEFand_ndi_launch:napariViewLightsheet.ndi.gui.app.genepyramid.clirunsndi/__init__.py, which loads numpy, pydantic_core, DID and about 1190 modules beforemain()is called.ndi.*, called frommain(), would therefore be weaker than the shell wrappers it replaces: those cleaned up before Python started.main()still callsescapeMatlabEnvironment()first, as requested. That coverspython -m ndi napari, and it does nothing after the console script has already re-exec'd._ndi_launch.py.UV_UNMANAGED_INSTALLas well asUV_INSTALL_DIRandUV_NO_MODIFY_PATH. I checked the cargo-dist template that generates Astral's installer. WithoutUV_UNMANAGED_INSTALLit writes~/.config/uv/uv-receipt.json(or%LOCALAPPDATA%\uv\…). That receipt would redirect a user's ownuv self updateto our pinned copy. CI asserts that no receipt is written.REG_EXPAND_SZ).SetEnvironmentVariableis used only to broadcast the change, which is also what Astral's installer does.[Environment]::SetEnvironmentVariable('Path', …, 'User')would rewrite the value asREG_SZ.%USERPROFILE%\AppData\Local\Microsoft\WindowsAppsentry.Additions beyond the brief:
~/.ndi/binis appended to the PATH, so the uv we ship never shadows a user's own uv.NDI_INSTALL_SOURCE,NDI_PYRAVIEW_FIND_LINKS,NDI_PYRAVIEW_CONSTRAINTS, andNDI_INSTALL_PYTHON. The last one exists for the Rosetta leg, see CI below.Part 1: MATLAB-safe console scripts
NDI_FROM_MATLABis set, orLD_*/DYLD_*names a MATLAB architecture directory (glnxa64,maci64,maca64), and the markerNDI_MATLAB_ENV_CLEANEDis not set.LD_LIBRARY_PATH,LD_PRELOAD,DYLD_LIBRARY_PATH,DYLD_FALLBACK_LIBRARY_PATH,DYLD_FRAMEWORK_PATH,DYLD_INSERT_LIBRARIES,PYTHONHOMEandPYTHONPATH. Each is justified in the code. It then callsos.execve(sys.executable, [sys.executable, *sys.orig_argv[1:]], env). It only execs when something was actually removed.pyenv. Signs of that are no__main__file, or on Linux/proc/self/exenot being a Python. Without this guard, an exec would replace MATLAB itself.PYTHONHOME/PYTHONPATHand MATLAB'sbin\win64-style directories are removed fromos.environin place, and only whenNDI_FROM_MATLABis set. Windows behaviour under MATLAB has not been measured, and the docstring says so.NDI_LAUNCH_DEBUG=1the final process prints one JSON line to stderr, reportingcleaned: reexec | in-place | nulland the remaining variables.tests/test_ndi_launch.pyhas 31 tests. Environment, platform, argv and execve are injected so nothing really execs, plus one real exec in a child process.tools/napariViewGEFandscripts/napariViewLightsheet, and updated every reference: pyproject, bothcli.py, the lightsheet README, four bridge YAMLs, the CHANGELOG, README and getting-started.INTERFACE UPDATE: I have modified the bridge contract for
preferences(src/ndi/ndi_matlab_python_bridge.yaml) to reflect the MATLAB source.GUI.GEFManager.ViewerLaunchernow defaults to"", meaning "find napariViewGEF automatically":~/.ndi/binfirst, then the PATH. Its description is updated, and the decision_log records that NDI-matlab makes the same change in a parallel PR. The hash is unchanged until that lands. In four other bridge entries only the decision_log changed, to note that the wrappers are gone; no hash moved.Part 2: the installer (
install/)install-ndi.sh(POSIX sh; shellcheck-clean):UV_VERSION="0.11.32") and fetcheshttps://astral.sh/uv/0.11.32/install.shonly if that version is not already in~/.ndi/bin;uv tool install --python 3.12 --reinstall --find-links <raw pyraview-wheels.html> --constraints <raw pyraview-constraints.txt> --no-build-package pyraview "ndi[gui,napari,pyraview] @ <source>"withUV_PYTHON_INSTALL_DIR,UV_TOOL_DIRandUV_TOOL_BIN_DIR. It also setsUV_PYTHON_PREFERENCE=only-managedandUV_NO_CONFIG=1;uv tool installaccepts every flag, including a remote-c.--help, andndi checkpasses. napari[all] brings pandas and matplotlib, so the full check applies.LD_LIBRARY_PATHcan break the system curl..zshrc/.zprofileor.bashrc/.profile, plus.bash_profileif it exists.--uninstallremoves only bin, python, venvs and that block, and restores each profile file byte for byte: the marker records whether the installer added a missing final newline or created the file.install-ndi.ps1: the same steps, for Windows PowerShell 5.1+. Empty entries in the User PATH (e.g. a trailing;) are kept, so add-then-remove gives back the identical string.Install NDI.commandis committed with mode 100755.Install NDI.cmdkeeps CRLF via.gitattributes.install/README.md: one page for end users, including the Gatekeeper right-click > Open step and the one-linecurl … | shandirm … | iexcommands.CI:
.github/workflows/installer.ymlinstall/**, pyproject, the launcher and the twocli.py; plusworkflow_dispatchand a weekly run.NDI_INSTALL_SOURCE, with git, cmake and the compilers hidden (the Install every runtime dependency from archive URLs, not git #328/Install Pyraview 0.4.2 from its pre-built release wheels, never from source #330 technique).NDI_INSTALL_PYTHON=cpython-3.12-macos-x86_64. Running the installer underarch -x86_64would not work: Astral's installer detects Rosetta with sysctl and installs the arm64 uv anyway.--help;tools/check_pyraview_wheel.py);NDI_FROM_MATLAB=1and junk loader/Python variables, the child reports"cleaned": "reexec"(Windows:"in-place") with every variable null;diff -rshows the rest of~/.ndi(a dummyNDI_Preferences.jsonandtools/), the profile files and the Windows User PATH are byte-identical to before.What was and wasn't verified locally
This sandbox gets 403 from github.com archive/release downloads and from astral.sh, so a real end-to-end install can only be proven in CI. Locally:
dashin a fake HOME, with three stand-ins:UV_OVERRIDE;ndi checkpassed. A re-run was idempotent, and uninstall left the prefs andtools/untouched.napariViewGEFre-exec'd correctly.pip install -e .(whatndi_install.pyruns) still gives an editable install fromsrc/, with the new console-script targets.black,ruff, the full pytest suite (5865 passed), actionlint and shellcheck are all clean.🤖 Generated with Claude Code
https://claude.ai/code/session_015KyLF4xAq4Sm4aT5gDyMTS