Chuyển đến nội dung chính

dsh-matlab-bridge

Đã xác minh

@alotop/dsh-matlab-bridge · v0.2.0 · MIT

Run and interactively debug MATLAB code from a DSH session, through a persistent MATLAB Engine session.

Cài đặt

dsh plugin add @alotop/dsh-matlab-bridge

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Thẻ

Tác giả

Readme

@alotop/dsh-matlab-bridge

在 DSH 会话里运行和交互式单步调试 MATLAB 代码,底层是一个常驻的 MATLAB Engine 会话。

npm license

English


为什么需要它

MATLAB 在 DSH 默认的 workspace-write 文件沙箱下根本无法启动。它在启动时会写工作区之外的位置(偏好设置、许可缓存),然后直接崩掉:

Fatal Startup Error: System error: File system inconsistency

把偏好目录重定向到可写位置也没用。所以用 shell 去驱动 matlab -batch,代价是每一次调用都要一次沙箱提权。

本插件改为通过非受限的 ctx.subprocess 通道启动驱动进程,这笔代价只需付一次——而且它保持一个 MATLAB Engine 会话存活,这正是一切调试能力的前提。

为什么必须常驻会话,而不是 matlab -batch

matlab -batch 每次调用都要约 20 秒冷启动,而且什么都不保留:变量、打开的图形、断点全部丢失。更关键的是它无法单步调试——断点命中时 MATLAB 自己的命令循环被阻塞,要发出 dbstep 必须有带外求值器,而官方 MATLAB Engine API 提供的正是这个。

DSH 会话 ──ctx.subprocess.spawn──▶ python ml_driver.py
                                    │ matlab.engine
                                    ▼
                          常驻、可调试的 MATLAB

环境要求

MATLAB R2020a 或更新(需要 exportgraphics)。任意标准安装即可,用的是它自带的 extern/engines/python。
Python 3.9 – 3.13,需在 PATH 上
Node.js 20 或更新

引擎自带的声明是支持 Python 3.9–3.12,但它附带的是**稳定 ABI(abi3)**扩展模块,实测在 3.13 上也能正常装载和运行。

安装

# 1. 把包装进你的 DSH profile
dsh plugin --profile <profile> add @alotop/dsh-matlab-bridge

# 2. 在「已安装的那份」里铺开 Engine 运行库
node "<profile>/node_modules/@alotop/dsh-matlab-bridge/scripts/setup-engine.mjs"

# 3. 重启 DSH,让新的 bundle 层被组装进去

第 2 步会从你已经装好的 MATLAB 里复制引擎,并生成引擎定位原生库所需的 _arch.txt。不下载任何东西,也不再分发任何 MathWorks 代码——详见 LICENSE。

第 2 步必须针对「已安装的那份」执行,不要跑仓库里的检出。 脚本会写到它自己旁边的 python/pylibs,而插件是从自己的安装位置加载运行库的——铺到别处,已安装的那份仍然是空的。同一份安装也提供 <profile>/node_modules/.bin/dsh-matlab-bridge-setup 这个命令。

为什么要声明 dsh.bundle

dsh plugin add 只有在包的 package.json 声明了 dsh.bundle 时,才会把它记为 profile layer。本包把它指向 cordis.patch.yml,由后者插入 matlab-bridge 这一行。没有它,安装照样成功但什么都不会注册,DSH 也会明确提示:declares no dsh.bundle — installed as a plain dependency, not a profile layer。

备选:写成 preset 行

同一个插件也可以不用 profile bundle,而是从 agent preset 组装。该行只注册工具、不发布服务,因此直接放在 preset 顶层即可,不需要 isolate realm。

- id: matlab-bridge
  name: '@alotop/dsh-matlab-bridge'

独立使用,或从检出安装

用于开发这个桥接本身,或者包还没发到 npm 之前:

npm pack                                                       # 得到 alotop-dsh-matlab-bridge-<版本>.tgz
npx ./alotop-dsh-matlab-bridge-<版本>.tgz                      # 直接从 tarball 跑安装命令
npm install -g ./alotop-dsh-matlab-bridge-<版本>.tgz           # 或全局安装
npm install --no-save ./alotop-dsh-matlab-bridge-<版本>.tgz    # 或装进某个工程

只想试一下 tarball 时记得加 --no-save;否则 npm 会在那个工程的 package.json 里记下一条指向 tarball 的 file: 依赖。

只有一个包。 dsh-matlab-bridge-setup 是它的一个 bin 入口,不是独立包——npx @alotop/dsh-matlab-bridge 之所以能跑起它,是因为它是该包唯一的可执行文件。

行配置

全部可选,正常机器上无需任何配置。

- id: matlab-bridge
  name: '@alotop/dsh-matlab-bridge'
  config:
    pythonPath: python3.12            # 默认:PATH 上第一个 python3 / python
    workDir: /path/to/project         # 默认:调用方会话的 cwd
    figureDir: /tmp/figures           # 默认:<workDir>/.matlab-figures
    timeoutMs: 180000                 # 默认:180000
    shutdownEngineOnExit: true        # 默认:true

工具

matlab_run

在常驻会话里运行 MATLAB 代码,返回命令窗口输出。变量、图形、数据跨调用保留。 用 disp/fprintf 打印,或者传入单个裸表达式让它显示值。失败时返回 MATLAB 错误信息与错误栈。

matlab_debug

驱动 MATLAB 调试器。动作:break、breakError、clearBreaks、run、status、stack、vars、get、eval、step、stepIn、stepOut、continue、quit、finish。

一次真实会话:

matlab_debug action=break file=myfunc line=12   → ok
matlab_debug action=run   code="myfunc(data)"   → state: paused
matlab_debug action=vars                        → 暂停帧的局部变量
matlab_debug action=get   name=startIndex       → 7
matlab_debug action=eval  code="n - k + 1"      → 8
matlab_debug action=step                        → state: paused(下一行)
matlab_debug action=continue                    → state: completed

eval 在暂停帧内部求值,所以可以在不改文件的前提下验证对某个活变量的推断。断点打在注释或空行上会绑定到下一个可执行行,与 MATLAB 编辑器行为一致。

matlab_figure

检查与导出图形。list 列出打开的 figure,save 把每张图导出为 150 DPI 的 PNG 并返回路径,close 关闭一张或全部。

先用 matlab_run 画图,再导出——figure 留在会话里。拿到路径后要用 read_image 把 PNG 读回来;只拿路径等于没看图。

matlab_session

start、status、stop。MATLAB 启动要几十秒,所以会话保持热态并复用。

status 会报告包路径、驱动实际使用的 Python 解释器、驱动是否在跑、引擎运行库是否已铺开、以及退出 DSH 时是否关闭 MATLAB。解释器值得显示出来——在有多个 Python 的机器上,PATH 上先解析到的那个不一定是 setup 验证过的那个,两者不一致时 pythonPath 就是解药。

命令

四个人工入口,用于那些不该出现在模型工具面上的操作:

命令 作用
/matlab-status 与 matlab_session status 同一份报告
/matlab-start 启动常驻引擎会话
/matlab-stop 关闭 MATLAB 并停止驱动
/matlab-setup 从本地 MATLAB 铺开引擎运行库

/matlab-setup 走的是与其它操作相同的非受限 subprocess 通道,因此它能写入已安装的包目录(位于任何会话工作区之外),不需要你另开终端。

DSH 退出时是否关闭 MATLAB

shutdownEngineOnExit(默认 true)决定关闭 DSH 时是否一并关闭 MATLAB。默认开启,因为驱动是子进程:直接杀掉它会留下一个无人认领的 MATLAB 引擎。

两条机制覆盖 DSH 两种退出方式:

  • 正常的插件卸载会写入关闭请求并关闭驱动的 stdin,驱动自己跑完 engine.quit() 后退出;
  • 如果 DSH 根本没卸载插件就退出,那条管道关闭会以 EOF 形式到达驱动,驱动同样退出 MATLAB 后再结束。

设为 false 可让 DSH 退出时保留 MATLAB。但在没有 shared engine 会话的前提下,没有任何东西能重接上去,它只会变成一个孤儿进程——适合临时查看一个活会话,不是"保留会话"的办法。

工作原理

文件 作用
src/plugin.mjs Cordis 插件:定位自身文件、持有驱动进程、注册工具
python/ml_driver.py 常驻驱动。stdin/stdout 上是行分隔 JSON,持有一个 MATLAB Engine 会话
python/mfiles/dsh_evalbase.m 在 base 工作区求值,复现命令窗口的回显语义
python/mfiles/dsh_figure_*.m 以 JSON 形式列举与导出 figure
scripts/setup-engine.mjs 从本地 MATLAB 铺开引擎运行库
python/selftest.py 对真实 MATLAB 的端到端自检

为什么协议要带 @@DSH: 前缀

MATLAB Engine 会把 MATLAB 命令窗口输出转发到驱动的 stdout,裸 JSON 协议会和这些输出抢同一个流。因此每一行协议都带 @@DSH: 前缀,不带前缀的行一律视为 MATLAB 输出(诊断信息),而不是去解析它。

命令窗口回显语义

dsh_evalbase.m 调用 evalc('evalin(''base'', code)'),并且刻意不给 evalin 输出参数。这才让捕获行为与命令窗口一致:赋值回显 x = 41,裸表达式回显 ans = 42,无值语句不输出。反过来,去要输出参数会把这三者全部抑制;而靠源码文本猜"是不是表达式"则会误判命令语法(如 dbstop in f at 4)和无输出参数的调用(如 disp('hi'))。

开发

git clone https://github.com/alotop/dsh-matlab.git
cd dsh-matlab
npm run setup      # 从本地 MATLAB 铺开引擎运行库
npm run selftest   # 对真实 MATLAB 的端到端检查(会启动一个)
npm run check      # 语法检查插件与脚本

npm run selftest 覆盖协议、输出捕获、错误上报、图形导出,以及完整的断点单步链路。它失败 = 驱动有问题,而不是插件或传输层的问题,故障域因此很小。

已知限制

  • 图形以文件交付,不是内联图像。 matlab_figure 返回 PNG 路径。工具结果理论上可以携带内联图像块,但那要走附件服务换取引用;路径加 read_image 已经能让图可见,且不依赖任何服务契约。
  • 驱动被强杀可能遗留 MATLAB 进程。 驱动在 atexit 里退出引擎,但被硬终止时 atexit 不会执行。用 matlab_session 的 action="stop" 正常收尾。
  • MATLAB 忙时查询会排队。 引擎在单线程上串行处理请求,所以后台运行未命中时发出的 dbstack 探测会阻塞到该调用让出控制权。run 已改为先查完成状态再探栈,规避了最糟的情况。
  • 单步调试目前只在 Windows 上验证过。 引擎铺开逻辑支持 glnxa64 与 maca64/maci64,但只有 Windows 跑过完整的端到端。欢迎其他平台的反馈。

许可

MIT。本包不再分发任何 MathWorks 代码;见许可文件末尾的说明。