Compare commits

...

132 Commits

Author SHA1 Message Date
cnc
4e246738ac 按源清理OPFS参数scratch
结论:依据 LinuxCNC rs274ngc_pre.cc restore_parameters() 只读取配置参数文件、interp_internal.hh 默认 rs274ngc.var/.bak 常量,浏览器 OPFS loadParameterFile 在加载存在的参数文件前清理 rs274ngc.var.new/.bak scratch,避免旧 WASM mirror 污染;缺失文件路径继续清空 scratch 并返回 null。

检查:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-06 11:47:33 +08:00
cnc
92562db29f 按源验证当前位置命名参数 2026-06-06 08:51:20 +08:00
cnc
de5f28f7ee 按源扩展坐标偏置UVW覆盖 2026-06-06 08:45:25 +08:00
cnc
82f222ba44 按源验证G10坐标UVW偏置 2026-06-06 08:38:23 +08:00
cnc
cdc30dda7e 按源验证G92单位切换UVW偏置 2026-06-06 08:35:11 +08:00
cnc
1d96aa8a55 按源验证单位切换UVW线性轴 2026-06-06 08:31:42 +08:00
cnc
25cb6885bf 按源修正外部单位换算
结论:依据 LinuxCNC emccanon.cc 与 units.h,GET_EXTERNAL_LENGTH_UNITS 返回 user-units-per-mm 而非当前程序单位;新增 G20/G21 后 G92.3 恢复覆盖,native/source-link/test-native 均通过。
2026-06-06 08:26:21 +08:00
cnc
ccd51ff373 按源同步单位切换当前位置
结论:依据 LinuxCNC interp_convert.cc convert_length_units 与 unit_tests/interp/test_interp_basics.cc,G20/G21 切换时事件桥同步重标定当前线性坐标,native/source-link 覆盖通过。
2026-06-06 08:15:59 +08:00
cnc
10bf0a5897 验证 INI G64 默认容差
提示词:快速推进,非纯 source-map-only,保留源依据、自动测试、本地提交且不自动推送。

结论:基于 LinuxCNC rs274ngc_pre.cc 读取 G64_DEFAULT_TOLERANCE/G64_DEFAULT_NAIVETOLERANCE 以及 interp_convert.cc convert_control_mode 在 G64 省略 P/Q 时使用默认值,新增 INI/G-code fixture 并接入 native/source-link 事件断言;./test-native.sh 和 ./test-linuxcnc-source-link.sh 通过。
2026-06-06 07:12:42 +08:00
cnc
ced3e5de0e 验证 INI 圆弧容差收紧
提示词:快速推进,非纯 source-map-only,保留源依据、自动测试、本地提交且不自动推送。

结论:基于 LinuxCNC rs274ngc_pre.cc 读取 CENTER_ARC_RADIUS_TOLERANCE_MM 以及 interp_convert.cc/interp_arc.cc IJK 圆弧半径误差检查,新增 metric arc fixture 和 tighter INI 覆盖默认通过、INI 收紧失败;./test-native.sh 和 ./test-linuxcnc-source-link.sh 通过。
2026-06-06 07:05:29 +08:00
cnc
7ba3a22441 验证 Fanuc 子程序禁用
提示词:快速推进,非纯 source-map-only,保留源依据、自动测试、本地提交且不自动推送。

结论:基于 LinuxCNC rs274ngc_pre.cc 读取 DISABLE_FANUC_STYLE_SUB 以及 interp_read.cc 对 M98、Fanuc-style M99、bare O-word 的禁用分支,新增 INI/G-code fixture 并接入 native/source-link 错误断言;./test-native.sh 和 ./test-linuxcnc-source-link.sh 通过。
2026-06-06 06:48:17 +08:00
cnc
5aa328b413 验证 G92 禁用持久化
提示词:快速推进,非纯 source-map-only,保留源依据、自动测试、本地提交且不自动推送。

结论:基于 LinuxCNC rs274ngc_pre.cc 的 DISABLE_G92_PERSISTENCE 初始化清理和 interp_convert.cc convert_stop 的 M30 清理逻辑,新增 INI/G-code fixture 并接入 native/source-link 参数持久化断言;./test-native.sh 和 ./test-linuxcnc-source-link.sh 通过。
2026-06-06 06:38:42 +08:00
cnc
586c28656d 验证 OPFS rename 持久清理
提示词:快速推进,避免纯 source-map-only,不自动推送。

结论:基于 LinuxCNC rs274ngc_pre.cc save_parameters() 的 filename + .new rename 语义,补强浏览器 OPFS moveFile 在新 WASM 实例中持久删除源文件并恢复目标 mirror 的覆盖;./test-native.sh 和 ./test-linuxcnc-source-link.sh 通过。
2026-06-06 06:31:30 +08:00
cnc
6efb77b8e4 验证 tooldata reset 保留索引边界 2026-06-06 06:26:46 +08:00
cnc
ebb29f8cb6 补齐 G5.3 非 G5.2 运动错误覆盖 2026-06-06 06:15:12 +08:00
cnc
8544872198 按源保护 OPFS 参数备份失败边界 2026-06-06 06:06:03 +08:00
cnc
e320d6d420 按源保护 PythonPlugin INI 节解析边界 2026-06-06 05:56:10 +08:00
cnc
ba2d680500 按源保护 PythonPlugin inittab 初始化边界 2026-06-06 05:49:45 +08:00
cnc
9af3443d95 按源保护 PythonPlugin 路径列表边界 2026-06-06 05:34:33 +08:00
cnc
4adcd82b74 按源保护 PythonPlugin TOPLEVEL 路径边界 2026-06-06 05:26:33 +08:00
cnc
66ea34a76d 验证 Boost.Python shim 惰性边界 2026-06-06 05:20:23 +08:00
cnc
5bc568eaa9 加强 PythonPlugin 负状态探针 2026-06-06 05:12:39 +08:00
cnc
23a4679c28 验证 tooldata NML 非空表注册路径 2026-06-06 05:08:00 +08:00
cnc
3c05202097 补齐刀补下偏置平面错误覆盖
结论:基于 LinuxCNC interp_convert.cc convert_axis_offsets()、convert_straight() 和 convert_set_plane(),native/source-link 现在覆盖刀补打开时 G92、G53、G18 被拒绝的失败分支。
2026-06-05 23:52:34 +08:00
cnc
ba0d973f4f 补齐刀补下模式切换错误覆盖
结论:基于 LinuxCNC interp_convert.cc convert_control_mode()、convert_coordinate_system() 和 convert_home(),native/source-link 现在覆盖刀补打开时 G64、G55、G28 被拒绝的失败分支。
2026-06-05 23:46:15 +08:00
cnc
f69e10bedf 补齐刀补下工具偏置错误覆盖
结论:基于 LinuxCNC interp_convert.cc convert_tool_length_offset(),native/source-link 现在覆盖 cutter radius comp 打开时 G43 工具长度偏置被拒绝的失败分支。
2026-06-05 23:41:09 +08:00
cnc
a8f6a9d907 补齐 probe 刀补错误覆盖
结论:基于 LinuxCNC interp_convert.cc convert_probe(),native/source-link 现在覆盖 cutter radius comp 打开时 G38 probe 被拒绝的失败分支。
2026-06-05 23:36:27 +08:00
cnc
0850954ca8 补齐 probe 转换错误覆盖
结论:基于 LinuxCNC interp_convert.cc convert_probe(),native/source-link 现在覆盖零进给、G95 feed-per-rev 和起点等于终点三类 G38 probe 失败分支。
2026-06-05 23:32:43 +08:00
cnc
6bd1c6a429 接入 WASM blocker 自检
结论:native/source-link 必跑入口已执行 blocker 分析器负路径自检,防止坏 manifest、缺失源根、重复条目和缺失替换映射退化。
2026-06-05 23:26:02 +08:00
cnc
e928b32e69 补齐 OPFS 镜像冲突覆盖 2026-06-05 23:20:06 +08:00
cnc
36763c6bcd 补齐 cutter-comp override M码覆盖 2026-06-05 23:15:13 +08:00
cnc
03080afe24 补齐 cutter-comp 剩余M码覆盖 2026-06-05 23:10:46 +08:00
cnc
120a294f62 补齐 cutter-comp M码 source-linked 覆盖 2026-06-05 23:06:13 +08:00
cnc
a3daa829fc 补齐 switchkins 输出路径检查 2026-06-05 22:46:15 +08:00
cnc
71c61fca84 补齐 tooldata 非随机同步 probe 2026-06-05 22:40:34 +08:00
cnc
ff2980569c 锁定 ABORT 热注释 source-linked 错误 2026-06-05 22:34:46 +08:00
cnc
695186ddfc 补齐 LinuxCNC 注释 source-linked 覆盖 2026-06-05 22:31:09 +08:00
cnc
8394042d18 补齐 OPFS 参数跨实例恢复覆盖 2026-06-05 22:20:10 +08:00
cnc
7e18c6cd40 补齐默认 M3M4 单主轴 source-linked 覆盖 2026-06-05 22:07:39 +08:00
cnc
09e2259669 补齐默认 M5 全主轴 source-linked 覆盖 2026-06-05 22:03:14 +08:00
cnc
867bf09c5d 补齐 M51 全主轴 source-linked 覆盖 2026-06-05 21:59:26 +08:00
cnc
d35766e387 补齐多主轴 G76 source-linked 覆盖 2026-06-05 21:55:31 +08:00
cnc
9c73577ade 补齐多主轴 M19 source-linked 覆盖 2026-06-05 21:50:59 +08:00
cnc
55149b0a0a 覆盖多主轴全选 spindle source-linked 路径 2026-06-05 21:47:28 +08:00
cnc
6a886be52d 保留多主轴 spindle canon selector 2026-06-05 21:43:22 +08:00
cnc
acbf5b4686 多主轴 G95/G96/G97 source-linked 覆盖 2026-06-05 21:34:43 +08:00
cnc
77c6674905 提示词:补齐 G33 多主轴源链接覆盖
结论:新增 [TRAJ] SPINDLES=2 的 source-linked fixture,验证 LinuxCNC interp_convert.cc 中 G33/G33.1 带  会把 active_spindle 传入 START_SPEED_FEED_SYNCH;INI 来源为 rs274ngc_pre.cc init 读取 INI_FILE_NAME。未扩展 smoke 语义。检查通过:./test-linuxcnc-source-link.sh;./test-native.sh。
2026-06-05 21:18:27 +08:00
cnc
eb91ced792 提示词:补齐 M51 多主轴源链接覆盖
结论:新增 [TRAJ] SPINDLES=2 的 source-linked fixture,验证 LinuxCNC interp_convert.cc 中 M51 带  只切换选定主轴的 speed override;INI 来源为 rs274ngc_pre.cc init 读取 INI_FILE_NAME。未扩展 smoke 语义。检查通过:./test-linuxcnc-source-link.sh;./test-native.sh。
2026-06-05 21:09:07 +08:00
cnc
1b60bc2b2e 提示词:验证 OPFS 程序跨独立挂载恢复
结论:新增浏览器 OPFS section,使用同一 workspace 但不同 WASM mount point 的新 simulator 解析已持久化程序,证明程序来自 OPFS 而非旧 WASM FS mirror。LinuxCNC 来源依据为 rs274ngc_pre.cc open()/read_text() 文件读取路径;未扩展 smoke 语义。检查通过:./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-05 20:59:43 +08:00
cnc
37879f2a67 提示词:锁定 naivecam tolerance Canon 事件 2026-06-05 16:04:23 +08:00
cnc
a7cd889b3a 提示词:补齐 G64 naivecam tolerance 源链接覆盖 2026-06-05 15:59:51 +08:00
cnc
4887b2f64e 提示词:补齐 PythonPlugin C API WASM shim 符号
结论:按 LinuxCNC src/emc/pythonplugin/python_plugin.cc 的初始化、导入、配置和 callable 调用路径,把 PyConfig/PyStatus、PyImport_*、PyDict_SetItemString、Py_InitializeFromConfig、PyRun_SimpleString、Py_GetVersion、PyObject_Call 等符号纳入 inert WASM Python C API shim;真实 Python 执行仍保持禁用。

检查:./test-linuxcnc-wasm-python-c-api-symbols.sh 通过;./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 15:51:51 +08:00
cnc
cb315ce1cd 提示词:验证 OPFS 参数备份跨实例恢复
结论:按 LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc save_parameters() 的 .bak 保存语义,强化浏览器 smoke,在新 WASM 实例中读取 OPFS 主参数文件和备份文件,证明备份不是当前实例镜像残留。

检查:./check-linuxcnc-opfs-source-map.sh 通过;./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 15:37:03 +08:00
cnc
9a4e0e3712 提示词:补齐 rtapi_vsnprintf WASM shim
结论:按 LinuxCNC src/rtapi/uspace_common.h 和 src/rtapi/rtapi.h,将 userspace rtapi_vsnprintf 适配为 vsnprintf 包装,并在 WASM runtime probe 覆盖 snprintf、va_list 和截断返回值。

检查:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 15:29:41 +08:00
cnc
98755528e4 使用刚更新的 AGENTS.md 和 align-linuxcnc 加速规则执行
结论:按 align-linuxcnc 先确认干净基线,避免 source-map-only 和 smoke 扩展;增强 WASM runtime link probe,覆盖 LinuxCNC src/emc/rs274ngc/gcodemodule.cc 与 src/emc/task/taskclass.cc 的 _task=0 preview 边界和 builtin_modules interpreter/emccanon 表结构。\n\n测试结论:./test-linuxcnc-wasm-runtime-link.sh、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-05 15:21:06 +08:00
cnc
504b9b6d23 使用刚更新的 AGENTS.md 和 align-linuxcnc 加速规则执行
结论:按 align-linuxcnc 先确认干净基线,避免 source-map-only 和 smoke 扩展;增强 WASM Python C API symbol probe,覆盖 LinuxCNC src/emc/rs274ngc/gcodemodule.cc、interp_python.cc、interp_namedparams.cc 等 RS274 Python/remap 源已使用的 inert API 返回边界,包括 PyObject_Str、Py*_Check、PySequence_*、PyObject_*、PyArg_ParseTuple 和 Py_BuildValue。\n\n测试结论:./test-linuxcnc-wasm-python-c-api-symbols.sh、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-05 15:15:42 +08:00
cnc
284afa1c64 使用刚更新的 AGENTS.md 和 align-linuxcnc 加速规则执行
结论:按 align-linuxcnc 先确认干净基线,避免 source-map-only 和 smoke 扩展;增强 WASM PythonPlugin link probe,锁定 LinuxCNC src/emc/pythonplugin/python_plugin.cc / python_plugin.hh 暴露的 last_errmsg()/last_exception() 状态面,在 Python 禁用边界下仍保持插件 API 诊断一致。\n\n测试结论:./test-linuxcnc-wasm-python-plugin-link.sh、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-05 15:11:32 +08:00
cnc
428fc46f4e 使用刚更新的 AGENTS.md 和 align-linuxcnc 加速规则执行
结论:按 align-linuxcnc 先确认干净基线,避免 source-map-only 和 smoke 扩展;增强 WASM interp_base link probe,锁定 LinuxCNC src/emc/rs274ngc/interp_base.cc 的绝对/相对 interpreter 路径失败诊断均到达 wasm-safe dlfcn unavailable 边界。\n\n测试结论:./test-linuxcnc-wasm-interp-base-link.sh、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-05 15:07:26 +08:00
cnc
d5c6b7555a 使用刚更新的 AGENTS.md 和 align-linuxcnc 加速规则执行
结论:按 align-linuxcnc 先确认干净基线,避免 source-map-only 和 smoke 扩展;新增 WASM tooldata 运行探针对 LinuxCNC src/emc/tooldata/tooldata_mmap.cc 随机换刀 T0 查找分支的覆盖,确认随机换刀扫描会越过 pocket 0 并返回非零 T0 pocket。\n\n测试结论:./test-linuxcnc-wasm-tooldata-link.sh、./test-linuxcnc-wasm-tooldata-runtime-symbols.sh、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-05 15:03:41 +08:00
cnc
b773946c45 执行 AGENTS align-linuxcnc 加速规则
结论:新增 LinuxCNC source-backed G95 zero-spindle-speed 错误覆盖,源码依据为 src/emc/rs274ngc/interp_convert.cc convert_straight()。

测试:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 11:15:05 +08:00
cnc
7a2a80ea71 执行 AGENTS align-linuxcnc 加速规则
结论:新增 LinuxCNC source-backed arc inverse-time missing-F 错误覆盖,源码依据为 src/emc/rs274ngc/interp_convert.cc convert_arc()。

测试:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 11:11:54 +08:00
cnc
41db47f440 执行 AGENTS align-linuxcnc 加速规则
结论:新增 LinuxCNC source-backed G1 inverse-time missing-F 错误覆盖,源码依据为 src/emc/rs274ngc/interp_convert.cc convert_straight()。

测试:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 11:08:11 +08:00
cnc
27ac0bfb25 执行 AGENTS align-linuxcnc 加速规则
结论:新增 LinuxCNC source-backed G1 zero-feed 错误覆盖,源码依据为 src/emc/rs274ngc/interp_convert.cc convert_straight()。

测试:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 11:04:55 +08:00
cnc
cd71eff8a5 执行 AGENTS align-linuxcnc 加速规则
结论:新增 LinuxCNC source-backed canned cycle cutter compensation 错误覆盖,源码依据为 src/emc/rs274ngc/interp_cycles.cc convert_cycle()。

测试:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 11:01:07 +08:00
cnc
eb1d31bfee 执行 AGENTS align-linuxcnc 加速规则
结论:新增 LinuxCNC source-backed canned cycle inverse-time feed 错误覆盖,源码依据为 src/emc/rs274ngc/interp_cycles.cc convert_cycle()。

测试:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-05 10:57:38 +08:00
cnc
2892f2ca9d 执行 AGENTS align-linuxcnc 加速规则
结论:补齐 WASM NML status shim 并增加可执行 runtime 探针,依据 LinuxCNC src/libnml/nml/nmlmsg.cc 和 stat_msg.cc 中 RCS_GENERIC_STATUS/RCS_STAT_MSG 构造初始化。验证通过:./test-linuxcnc-wasm-runtime-link.sh、./test-native.sh、./test-linuxcnc-source-link.sh。
2026-06-05 10:44:31 +08:00
cnc
a301965452 执行 AGENTS align-linuxcnc 加速规则
结论:补强 WASM runtime 可执行探针,依据 LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc 中程序/子程序路径解析对 wordexp() 的使用,覆盖  形式的环境变量展开。验证通过:./test-linuxcnc-wasm-runtime-link.sh、./test-native.sh、./test-linuxcnc-source-link.sh。
2026-06-05 10:31:33 +08:00
cnc
0737d5ea65 执行 AGENTS align-linuxcnc 加速规则
结论:补强 WASM Python C API 可执行探针,依据 LinuxCNC src/emc/rs274ngc/gcodemodule.cc、interpmodule.cc 和 py*.cc 对 PyObject_New、Py_TYPE、Py_None、PyList_Type 等 Python C API 的绑定用法。验证通过:./test-linuxcnc-wasm-python-c-api-symbols.sh、./test-native.sh、./test-linuxcnc-source-link.sh。
2026-06-05 10:23:49 +08:00
cnc
3a478ca335 执行 AGENTS align-linuxcnc 加速规则
结论:补强 WASM interp_base 可执行探针,依据 LinuxCNC src/emc/rs274ngc/interp_base.cc 中绝对解释器路径直接 dlopen、相对路径经 EMC2_HOME/lib/linuxcnc 解析后 dlopen 的分支。验证通过:./test-linuxcnc-wasm-interp-base-link.sh、./test-native.sh、./test-linuxcnc-source-link.sh。
2026-06-05 10:13:27 +08:00
cnc
a144576e90 执行 AGENTS align-linuxcnc 加速规则
结论:补强 WASM PythonPlugin 可执行探针,依据 LinuxCNC src/emc/pythonplugin/python_plugin.cc 中 configure() 对 INI_FILE_NAME 缺失和环境变量 INI 文件的分支。验证通过:./test-linuxcnc-wasm-python-plugin-link.sh、./test-native.sh、./test-linuxcnc-source-link.sh。
2026-06-05 10:06:39 +08:00
cnc
60745adeef 执行 AGENTS align-linuxcnc 加速规则
结论:补强 WASM tooldata 可执行探针,依据 LinuxCNC src/emc/tooldata/tooldata_mmap.cc 非随机刀库 T0 查找优先返回 pocket 0 的源行为。验证通过:./test-linuxcnc-wasm-tooldata-link.sh、./test-native.sh、./test-linuxcnc-source-link.sh。
2026-06-05 09:59:06 +08:00
cnc
6506d23dc2 继续按提示词推进:补tooldata mmap单创建者覆盖 2026-06-05 09:51:37 +08:00
cnc
440a61be32 尽快推进:收紧Python C API shim源码锚点 2026-06-05 09:38:13 +08:00
cnc
9b9e942dd6 尽快推进:收紧tooldata mmap shim源码锚点 2026-06-05 09:33:08 +08:00
cnc
3c72356c92 尽快推进:收紧runtime shim源码锚点 2026-06-05 09:28:22 +08:00
cnc
a89c107676 尽快推进:收紧EMC状态shim源码锚点 2026-06-05 09:19:23 +08:00
cnc
035b25862a 尽快推进:收紧dlfcn shim源码锚点 2026-06-05 09:15:41 +08:00
cnc
167703b87a 继续按提示词推进:收紧NML状态shim源码锚点 2026-06-05 09:11:30 +08:00
cnc
b600d6a516 尽快推进:收紧gettext shim源码锚点 2026-06-05 09:03:41 +08:00
cnc
0a2d37fae8 尽快推进:收紧RTAPI格式化shim源码锚点 2026-06-05 08:58:58 +08:00
cnc
111cb5f9b9 尽快推进:收紧RCS打印shim源码锚点 2026-06-05 08:54:01 +08:00
cnc
218ac523b8 尽快推进:收紧OPFS程序读取源码锚点 2026-06-05 08:48:46 +08:00
cnc
abaebe7940 尽快推进:收紧tooldata工具源码锚点 2026-06-05 08:43:29 +08:00
cnc
77e92b0116 尽快推进:收紧WASM blocker参数文件源码锚点 2026-06-05 08:38:29 +08:00
cnc
cf3154b043 尽快推进:同步WASM smoke OPFS边界 2026-06-05 08:30:47 +08:00
cnc
9da6ffcd6a 尽快推进:收紧浏览器后端边界 2026-06-05 08:25:48 +08:00
cnc
c71d2964ee 尽快推进:收紧OPFS smoke边界 2026-06-05 08:20:31 +08:00
cnc
c1a130f5fb 尽快推进:收紧Python shim禁执行边界 2026-06-05 08:15:17 +08:00
cnc
baf8429736 尽快推进:收紧tooldata外部DB边界 2026-06-05 08:09:06 +08:00
cnc
2e2fb34cc3 尽快推进:收紧HAL运行时shim边界 2026-06-05 08:02:20 +08:00
cnc
810bd92ddc 尽快推进:锁定dlopen shim映射 2026-06-05 07:56:33 +08:00
cnc
6d99ca67a9 尽快推进:收紧PythonPlugin禁用边界
结论:对齐LinuxCNC src/emc/pythonplugin/python_plugin.cc initialize() 负状态语义,WASM安全shim在Python不可用时返回PLUGIN_PYTHON_NOT_INITIALIZED并保持负状态短路;未实现Python/remap执行,未扩展smoke parser。
2026-06-05 07:50:13 +08:00
cnc
6a2f63c894 尽快推进:加强OPFS参数备份目录边界验证
结论:真实浏览器app smoke继续使用LinuxCNC rs274ngc_pre.cc save_parameters() 语义,验证OPFS下备份link失败非致命时主参数仍持久化且目录不被误删;未扩展smoke parser功能。
2026-06-05 07:39:12 +08:00
cnc
bc80d74d1f 参考所有分组,完成尽量多的内容:收紧build source-map边界守卫 2026-06-05 07:29:30 +08:00
cnc
71676ac1be 参考所有分组,完成尽量多的内容:收紧WASM smoke source-map边界守卫 2026-06-05 07:24:49 +08:00
cnc
89af73298e 参考所有分组,完成尽量多的内容:收紧WASM blockers source-map边界守卫 2026-06-05 07:20:05 +08:00
cnc
dc5cdeacc2 参考所有分组,完成尽量多的内容:收紧WASM shims source-map边界守卫 2026-06-05 07:15:05 +08:00
cnc
b170ccd95a 参考所有分组,完成尽量多的内容:收紧tooldata source-map边界守卫 2026-06-05 07:10:30 +08:00
cnc
29197b25cc 参考所有分组,完成尽量多的内容:收紧OPFS source-map边界守卫 2026-06-05 07:06:14 +08:00
cnc
5ce3c18103 参考所有分组,完成尽量多的内容:收紧browser-app source-map边界守卫 2026-06-05 07:01:37 +08:00
cnc
79077fa695 参考所有分组,完成尽量多的内容:收紧switchkins source-map边界守卫 2026-06-05 06:57:11 +08:00
cnc
f64db24060 参考所有分组,完成尽量多的内容:收紧kinematics source-map边界守卫 2026-06-05 06:52:42 +08:00
cnc
fb2b5c4920 参考所有分组,完成尽量多的内容:收紧RS274 source-map边界守卫 2026-06-05 06:48:36 +08:00
cnc
9c52e426f7 参考所有分组,完成尽量多的内容:闭环OPFS类型source-map守卫 2026-06-05 06:40:58 +08:00
cnc
d80405ccb3 参考所有分组,完成尽量多的内容:收紧 switchkins source-only API 守卫
结论:未扩展 smoke 功能行为,仅把 switchkins/remap source-map guard 的负向 API 检查从 TOOL_TABLE 扩展到 asset、metadata、tooldata 等 source-only 输入。

LinuxCNC 来源依据:configs/sim/**.ini 的 M428/M429/M430 REMAP、remap_subs/{428,429,430}remap.ngc 的 #<kinstype>、src/hal/utils/halcmd_commands.cc do_loadusr_cmd() 与 halcmd_completion.c 的 loadusr 选项模型。

检查通过:./check-linuxcnc-switchkins-remap-source-map.sh;./check-linuxcnc-switchkins-remap-table.sh linuxcnc-kinematics-source-files.txt;./check-linuxcnc-kinematics-source-map.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-05 06:32:36 +08:00
cnc
a48c02f5cc 参考所有分组,完成尽量多的内容:收紧 tooldata WASM probe 顺序守卫
结论:未扩展 smoke 功能行为,仅让 tooldata source-map guard 校验 build-wasm.sh 中四个 tooldata link/symbol probe 都在复制浏览器 artifact 前运行。

LinuxCNC 来源依据:src/emc/tooldata/Submakefile、tooldata_common.cc、tooldata_mmap.cc、tooldata_db.cc、tooldata_nml.cc、tool_mmap_read.cc、tool_watch.cc 与 tooldata.hh 的 tooldata 构建/导出边界。

检查通过:./check-linuxcnc-tooldata-source-map.sh;./check-linuxcnc-build-source-map.sh;./check-linuxcnc-workstreams-source-map.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-05 06:25:33 +08:00
cnc
f56468b664 参考所有分组,完成尽量多的内容:闭环 workstreams source-map 索引
结论:未扩展 smoke 功能行为,仅让 architecture、source-policy 与 workstreams source-map 形成双向索引,并由 check-linuxcnc-workstreams-source-map.sh 守卫。

LinuxCNC 来源依据:configs/sim/axis/vismach/5axis/bridgemill/5axis.ini、remap_subs/428remap.ngc、src/emc/kinematics/Submakefile、kinematics.h、src/emc/rs274ngc/interp_internal.hh、rs274ngc_pre.cc、rs274ngc/Submakefile、tooldata/Submakefile、ini/Submakefile、pythonplugin/Submakefile 的六分组边界。

检查通过:./check-linuxcnc-workstreams-source-map.sh;./check-linuxcnc-build-source-map.sh;./test-native.sh;./test-linuxcnc-source-link.sh;CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh。
2026-06-05 06:19:46 +08:00
cnc
ba2d576f8b 参考所有分组,完成尽量多的内容:收紧构建入口 source-map 守卫
结论:未扩展 smoke 功能行为,仅把 build-policy guard 扩展为同时校验 test-native.sh、test-linuxcnc-source-link.sh、build-wasm.sh 运行同一组 source-map guards。

LinuxCNC 来源依据:src/emc/rs274ngc/Submakefile、src/emc/tooldata/Submakefile、src/emc/ini/Submakefile、src/emc/pythonplugin/Submakefile、src/emc/kinematics/Submakefile 与 kinematics.h 的构建/源码链接边界。

检查通过:./check-linuxcnc-build-source-map.sh;./check-linuxcnc-workstreams-source-map.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-05 06:12:59 +08:00
cnc
7f45abe93c 参考所有分组,完成尽量多的内容:同步 OPFS 与 smoke 映射
结论:未扩展 smoke 功能行为,仅收紧 OPFS workspace、directory、policy 浏览器烟测和 source-map 守卫。

LinuxCNC 来源依据:src/emc/rs274ngc/rs274ngc_pre.cc 的 restore_parameters/save_parameters/read_text 参数文件与文件读取语义,以及 interp_internal.hh 的 rs274ngc.var/.bak 常量边界。

检查通过:./check-linuxcnc-opfs-source-map.sh;./check-linuxcnc-wasm-smoke-source-map.sh;./check-linuxcnc-workstreams-source-map.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-05 06:07:14 +08:00
cnc
ae7de73a7c 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补强 WASM runtime shim 的 LinuxCNC HAL source-map 与 probe,确认 HAL lookup 在浏览器安全 shim 中保持不可用边界,不扩展 smoke 行为。
2026-06-04 20:23:50 +08:00
cnc
7471444781 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定dlfcn禁用边界
结论:按 LinuxCNC src/emc/rs274ngc/interp_base.cc 的 dlopen/dlsym/dlerror 调用点,补强 WASM dlfcn shim 与 interp_base probe,确认浏览器安全探针中动态加载保持不可用;不扩展 smoke 解析。
2026-06-04 19:59:14 +08:00
cnc
f36e2a96c4 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定PythonPlugin负状态短路
结论:按 LinuxCNC src/emc/pythonplugin/python_plugin.cc 的 status < PLUGIN_OK 分支,补齐 WASM PythonPlugin shim 的 call/call_method 负状态短路探针;保持 Python 执行禁用,不扩展 smoke 解析。
2026-06-04 19:53:50 +08:00
cnc
aaa6dce99a 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定tooldata未激活DB语义
结论:按 LinuxCNC src/emc/tooldata/tooldata_db.cc 锁定 tooldata_db_getall() 未激活返回 -1、tooldata_db_notify() 未激活返回 0 的 WASM shim 探针与 source-map 守卫;不启用外部 DB 服务,不扩展 smoke 解析。
2026-06-04 19:48:47 +08:00
cnc
b89d6311ff 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定OPFS参数保存顺序
结论:按 LinuxCNC rs274ngc_pre.cc save_parameters() 的 .new、.bak、unlink/link/rename 顺序补强 OPFS/app/browser smoke 守卫,并验证临时参数文件不会残留在 OPFS。
2026-06-04 19:42:49 +08:00
cnc
a12c0166b5 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定OPFS覆盖条目 2026-06-04 19:22:34 +08:00
cnc
9fd23192dd 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定kinematics源码锚点 2026-06-04 19:15:54 +08:00
cnc
a46910b6c0 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定WASM阻塞替换集合 2026-06-04 19:07:21 +08:00
cnc
0b387db8ca 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定浏览器OPFS段名守卫 2026-06-04 18:57:28 +08:00
cnc
22c1c311ff 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定build-wasm源码映射前置
结论:build-wasm.sh 现在与 native/source-link 一样先运行六分组 source-map guards,再消费生成配置别名和浏览器 artifacts;同步 build/workstreams source map 与 aggregate 元检查。未新增 CNC 行为,未扩展 smoke fallback。\n\n测试:./check-linuxcnc-workstreams-source-map.sh;./check-linuxcnc-build-source-map.sh;git diff --check;CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-04 18:48:46 +08:00
cnc
3079656cfb 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:接入WASM边界源码映射
结论:将 WASM shims/blockers source-map guard 接入 native/source-link 主检查,并取消 workstreams guard 对这两个六分组 guard 的例外;仅加强源映射边界检查,未新增 CNC 行为,未扩展 smoke fallback。\n\n测试:./check-linuxcnc-workstreams-source-map.sh;./check-linuxcnc-wasm-shims-source-map.sh;./check-linuxcnc-wasm-blockers-source-map.sh;git diff --check;CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-04 18:39:06 +08:00
cnc
7502684780 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定六分组策略边界
结论:把六分组 workstreams 总索引与 docs/linuxcnc-source-policy.md 的单上下文推进、OPFS-only、smoke 仅验证等边界做机器检查绑定;未新增 CNC 行为,未扩展 smoke fallback。\n\n测试:./check-linuxcnc-workstreams-source-map.sh;git diff --check;CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-04 18:14:23 +08:00
cnc
340966b6f7 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 LinuxCNC tooldata 源映射 guard,并接入 native/source-link/workstreams 检查;仅验证源覆盖、WASM-safe shim/probe 边界和 TOOL_TABLE source-only 策略,未扩展 smoke 行为。\n\n测试:./check-linuxcnc-tooldata-source-map.sh;./check-linuxcnc-workstreams-source-map.sh;git diff --check;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-04 18:07:35 +08:00
cnc
1f314b7e9e 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定六分组源码映射总索引结论 2026-06-04 16:54:49 +08:00
cnc
1bfb7426d4 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定浏览器应用源码映射结论 2026-06-04 16:45:01 +08:00
cnc
abd96dfdde 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定构建源码映射结论 2026-06-04 16:37:24 +08:00
cnc
7b171c605b 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke:锁定WASM smoke源码映射结论 2026-06-04 16:26:51 +08:00
cnc
cb22ecd8ef 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 LinuxCNC WASM-safe shim source-map,锁定 linuxcnc_wasm_safe_probe_shims、RS274/WASM manifest、每个 shim 的 LinuxCNC 源依据与聚合 guardrails;未扩展 smoke 行为。验证通过:./check-linuxcnc-wasm-shims-source-map.sh linuxcnc-rs274-wasm-source-files.txt、./check-linuxcnc-wasm-blockers-source-map.sh linuxcnc-rs274-wasm-source-files.txt、./test-linuxcnc-wasm-cmake-safe-probe.sh、./test-native.sh、./test-linuxcnc-source-link.sh、./test-linuxcnc-source-syntax.sh、CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh。
2026-06-04 16:08:32 +08:00
cnc
a0f6228908 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 LinuxCNC WASM blocker source-map,锁定 RS274/WASM manifest、Python/Boost.Python、dlopen、tooldata mmap、native-fs 分类与 wasm-safe shim replacement;未扩展 smoke 行为。验证通过:./check-linuxcnc-wasm-blockers-source-map.sh linuxcnc-rs274-wasm-source-files.txt、./test-linuxcnc-wasm-blockers.sh linuxcnc-rs274-wasm-source-files.txt、./test-native.sh、./test-linuxcnc-source-link.sh、CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh、./test-linuxcnc-source-syntax.sh、./test-linuxcnc-wasm-cmake-safe-probe.sh。
2026-06-04 15:54:57 +08:00
cnc
8fe468ba3c 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 switchkins/remap 源映射守卫,接入 native/source-link/聚合检查;未扩展 smoke 行为。验证通过:./check-linuxcnc-switchkins-remap-source-map.sh、./check-linuxcnc-switchkins-remap-table.sh linuxcnc-kinematics-source-files.txt、./test-native.sh、./test-linuxcnc-source-link.sh、CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh。
2026-06-04 15:36:19 +08:00
cnc
b3b1117ec9 参考所有分组,完成尽量多的内容:锁定OPFS源码映射结论
结论:新增 OPFS source-map 文档和同步守卫,绑定 interp_internal.hh 默认参数文件、rs274ngc_pre.cc restore/save 语义与浏览器 OPFS 边界;未扩展 smoke 功能。
2026-06-04 14:48:07 +08:00
cnc
4eb694164f 参考所有分组,完成尽量多的内容:锁定kinematics源码映射结论
结论:新增 kinematics source-map 文档和同步检查,固定 manifest 分组计数与 source-coverage 边界,并接入 native/all-native guardrail;未扩展 smoke 功能。
2026-06-04 14:33:52 +08:00
cnc
59b8e2a832 参考所有分组,完成尽量多的内容:锁定RS274源码映射结论
结论:补齐 WASM header、blocked-header、metadata 的 source map 文档闭环,并把 manifest/source-map 同步检查接入 native 必跑路径;未扩展 smoke 功能。
2026-06-04 14:20:29 +08:00
128 changed files with 5896 additions and 128 deletions

View File

@@ -8,8 +8,8 @@ cd "$(dirname "$0")"
# src/emc/rs274ngc/gcodemodule.cc, interp_namedparams.cc, interp_o_word.cc,
# and the py*.cc module bindings; tooldata/native-backend blockers come from
# src/emc/tooldata/tooldata_mmap.cc and tooldata_db.cc; native filesystem
# blockers come from LinuxCNC RS274 sources such as interp_o_word.cc and
# interp_write.cc.
# coverage comes from LinuxCNC RS274 sources such as interp_o_word.cc
# directory traversal and rs274ngc_pre.cc parameter-file replacement.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}

View File

@@ -7,7 +7,7 @@ cd "$(dirname "$0")"
# rs274/tooldata sources directly or link wasm-safe shims whose source basis is
# checked against LinuxCNC source comments before building.
usage() {
echo "usage: $0 --mode tooldata-link|tooldata-common-link|interp-base-link|python-plugin-link|rs274ngc-pre-object" >&2
echo "usage: $0 --mode tooldata-link|tooldata-common-link|interp-base-link|python-plugin-link|python-plugin-inittab-link|rs274ngc-pre-object" >&2
}
mode=
@@ -33,7 +33,7 @@ while [[ "$#" -gt 0 ]]; do
esac
done
case "$mode" in
tooldata-link|tooldata-common-link|interp-base-link|python-plugin-link|rs274ngc-pre-object)
tooldata-link|tooldata-common-link|interp-base-link|python-plugin-link|python-plugin-inittab-link|rs274ngc-pre-object)
;;
"")
usage
@@ -114,14 +114,21 @@ case "$mode" in
)
target_name=interp_base_probe
;;
python-plugin-link)
python-plugin-link|python-plugin-inittab-link)
python_plugin_shim=$(./list-linuxcnc-wasm-safe-shims.sh python_plugin.cc)
grep -F 'LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and' core/wasm_shims/pythonplugin/python_plugin_probe_main.cc >/dev/null
if [[ "$mode" == "python-plugin-link" ]]; then
probe_source=core/wasm_shims/pythonplugin/python_plugin_probe_main.cc
target_name=python_plugin_probe
grep -F 'LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and' "$probe_source" >/dev/null
else
probe_source=core/wasm_shims/pythonplugin/python_plugin_inittab_probe_main.cc
target_name=python_plugin_inittab_probe
grep -F 'LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc' "$probe_source" >/dev/null
fi
sources+=(
"$python_plugin_shim"
core/wasm_shims/pythonplugin/python_plugin_probe_main.cc
"$probe_source"
)
target_name=python_plugin_probe
;;
rs274ngc-pre-object)
rs274ngc_pre_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/rs274ngc/rs274ngc_pre.cc)

View File

@@ -26,6 +26,17 @@ fi
preflight_cache_dir=build/wasm-preflight-cache
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh --cache-dir "$preflight_cache_dir" "$manifest"
./check-linuxcnc-rs274-source-map.sh
./check-linuxcnc-kinematics-source-map.sh
./check-linuxcnc-opfs-source-map.sh
./check-linuxcnc-switchkins-remap-source-map.sh
./check-linuxcnc-wasm-smoke-source-map.sh
./check-linuxcnc-build-source-map.sh
./check-linuxcnc-browser-app-source-map.sh
./check-linuxcnc-workstreams-source-map.sh
./check-linuxcnc-tooldata-source-map.sh
./check-linuxcnc-wasm-shims-source-map.sh
./check-linuxcnc-wasm-blockers-source-map.sh
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-switchkins-remap-table-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-kinematics-source-files.txt
manifest=$(cd "$(dirname "$manifest")" && pwd)/$(basename "$manifest")
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)

View File

@@ -0,0 +1,130 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties the browser app entry point to
# LinuxCNC RS274 parameter-file defaults, rs274ngc_pre.cc restore/save/read
# file semantics, and the existing OPFS-backed wasm-core bridge. It validates
# app integration policy only; it does not add CNC behavior or expand smoke
# parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F '#define RS274NGC_PARAMETER_FILE_NAME_DEFAULT "rs274ngc.var"' \
"$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F '#define RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX ".bak"' \
"$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F 'int Interp::restore_parameters(const char *filename)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'int Interp::save_parameters(const char *filename,' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'std::string tempfile = std::string(filename) + ".new";' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F '+ RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX;' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'unlink(bakfile.c_str());' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'if(link(filename, bakfile.c_str()) < 0)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'if(rename(tempfile.c_str(), filename) < 0)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'read_text(command, _setup.file_pointer, _setup.linetext,' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'LinuxCNC browser app source map' docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F 'must not add CNC behavior' docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F 'must not expand the temporary smoke parser' docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F '`RS274NGC_PARAMETER_FILE_NAME_DEFAULT` as `rs274ngc.var`' \
docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F '`parseFileWithParameterFile(programPath, parameterPath, "linuxcnc", ...)`' \
docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F 'orders parameter saves as backup unlink' \
docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F '<script type="module" src="/src/app.js"></script>' web/index.html >/dev/null
grep -F '<option value="linuxcnc-rs274">LINUXCNC</option>' web/index.html >/dev/null
if [[ "$(grep -Fc '<option value=' web/index.html)" -ne 1 ]]; then
echo "browser app backend selector must expose only the LinuxCNC RS274 backend" >&2
exit 1
fi
grep -F 'import { createWasmSimulator } from "./wasm-core.js";' web/src/app.js >/dev/null
grep -F 'const programPath = "programs/current.ngc";' web/src/app.js >/dev/null
grep -F 'const linuxCncParameterFileName = "rs274ngc.var";' web/src/app.js >/dev/null
grep -F 'const parameterPath = `parameters/${linuxCncParameterFileName}`;' web/src/app.js >/dev/null
grep -F 'const linuxCncBackend = "linuxcnc-rs274";' web/src/app.js >/dev/null
grep -F 'const opfsOptions = { required: true, mountPoint: "/cnc", workspacePath: "cnc-simulator" };' \
web/src/app.js >/dev/null
grep -F 'simulatorPromise = createWasmSimulator({' web/src/app.js >/dev/null
grep -F 'opfs: opfsOptions,' web/src/app.js >/dev/null
grep -F 'OPFS workspace is required for browser program parsing' web/src/app.js >/dev/null
grep -F 'await simulator.fs.opfs.writeFile(programPath, elements.input.value);' web/src/app.js >/dev/null
grep -F 'const events = await simulator.parseFileWithParameterFile(programPath, parameterPath, "linuxcnc", parseOptions);' \
web/src/app.js >/dev/null
grep -F 'elements.backendSelect.value = linuxCncBackend;' web/src/app.js >/dev/null
grep -F 'globalThis.__cncSimulatorForTest = simulatorPromise;' web/src/app.js >/dev/null
grep -F 'app frame did not persist current program into OPFS' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not persist LinuxCNC parameter state into OPFS' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not persist LinuxCNC parameter backup into OPFS' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not clean up LinuxCNC temporary parameter file in OPFS' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'OPFS PROGRAM RESTORED' web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not reload LinuxCNC parameter state from OPFS after reload' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'runSection("browser app opfs save"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("browser app opfs restore"' test-web-wasm-browser-smoke.sh >/dev/null
if grep -R -n -E 'localStorage|sessionStorage|indexedDB|showOpenFilePicker|showSaveFilePicker|showDirectoryPicker|webkitRequestFileSystem|FileReader' web/src; then
echo "browser app/wasm code must not add filesystem persistence outside OPFS" >&2
exit 1
fi
python3 - <<'PY'
from pathlib import Path
source_map = Path("docs/linuxcnc-browser-app-source-map.md").read_text(encoding="utf-8")
for entry in [
"web/index.html",
"web/src/app.js",
"web/test-browser-wasm-smoke-app-sections.js",
]:
if f"| `{entry}` |" not in source_map:
raise SystemExit(f"docs/linuxcnc-browser-app-source-map.md missing app entry: {entry}")
for phrase in [
"must not add CNC behavior",
"must not expand the temporary smoke parser",
"existing `createWasmSimulator()` OPFS API",
"programs/current.ngc",
"parameters/rs274ngc.var",
"visible backend selector must stay limited",
"./check-linuxcnc-browser-app-source-map.sh",
"does not interpret G-code",
]:
if phrase not in source_map:
raise SystemExit(f"docs/linuxcnc-browser-app-source-map.md missing boundary phrase: {phrase}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-browser-app-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-browser-app-source-map.sh")
app = Path("web/src/app.js").read_text(encoding="utf-8")
if "parse(" in app.replace("parseProgram", ""):
raise SystemExit("browser app should route program execution through parseFileWithParameterFile, not direct parse")
if app.count("elements.backendSelect.value = linuxCncBackend;") != 2:
raise SystemExit("browser app backend selector must only be forced to the LinuxCNC backend")
if "backendSelect.value" in app.replace("elements.backendSelect.value = linuxCncBackend;", ""):
raise SystemExit("browser app must not read the backend selector as a dynamic backend source")
if 'backend: linuxCncBackend' not in app:
raise SystemExit("browser app parse options must force the LinuxCNC backend")
PY

View File

@@ -0,0 +1,202 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties native/source-link/WASM build entry
# points to LinuxCNC Submakefile source ownership, source manifests, persistent
# build signatures, and source-backed WASM artifact ordering. It validates
# build policy only; it does not add CNC behavior, expand smoke parsing, or
# interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F 'LIBRS274SRCS := $(addprefix emc/rs274ngc/,' \
"$linuxcnc_root/src/emc/rs274ngc/Submakefile" >/dev/null
grep -F 'rs274ngc_pre.cc \' "$linuxcnc_root/src/emc/rs274ngc/Submakefile" >/dev/null
grep -F '../lib/libtooldata.so.0' "$linuxcnc_root/src/emc/rs274ngc/Submakefile" >/dev/null
grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_common.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_mmap.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_db.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'LIBLCNCINISRCS := emc/ini/inifile.cc' \
"$linuxcnc_root/src/emc/ini/Submakefile" >/dev/null
grep -F 'LIBPPSRCS := $(addprefix emc/pythonplugin/,' \
"$linuxcnc_root/src/emc/pythonplugin/Submakefile" >/dev/null
grep -F 'GENSERKINSSRCS := emc/kinematics/ugenserkins.c' \
"$linuxcnc_root/src/emc/kinematics/Submakefile" >/dev/null
grep -F 'extern int kinematicsInverse(const struct EmcPose * world,' \
"$linuxcnc_root/src/emc/kinematics/kinematics.h" >/dev/null
grep -F 'LinuxCNC build source map' docs/linuxcnc-build-source-map.md >/dev/null
grep -F 'must not add CNC' docs/linuxcnc-build-source-map.md >/dev/null
grep -F 'must not expand the temporary smoke parser' docs/linuxcnc-build-source-map.md >/dev/null
grep -F '`src/emc/rs274ngc/Submakefile` defines `LIBRS274SRCS`' \
docs/linuxcnc-build-source-map.md >/dev/null
grep -F '`src/emc/tooldata/Submakefile` defines the LinuxCNC tooldata' \
docs/linuxcnc-build-source-map.md >/dev/null
grep -F 'Default WASM manifest partition must remain 26 wasm-safe `core` sources and' \
docs/linuxcnc-build-source-map.md >/dev/null
grep -F '`build-wasm.sh` must configure with `CNC_SIM_ENABLE_SMOKE_BACKEND=OFF`' \
docs/linuxcnc-build-source-map.md >/dev/null
grep -F '`test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must' \
docs/linuxcnc-build-source-map.md >/dev/null
grep -F 'LinuxCNC source basis: the wasm build preflights and compiles the' build-wasm.sh >/dev/null
source_map_guards=(
'./check-linuxcnc-rs274-source-map.sh'
'./check-linuxcnc-kinematics-source-map.sh'
'./check-linuxcnc-opfs-source-map.sh'
'./check-linuxcnc-switchkins-remap-source-map.sh'
'./check-linuxcnc-wasm-smoke-source-map.sh'
'./check-linuxcnc-build-source-map.sh'
'./check-linuxcnc-browser-app-source-map.sh'
'./check-linuxcnc-workstreams-source-map.sh'
'./check-linuxcnc-tooldata-source-map.sh'
'./check-linuxcnc-wasm-shims-source-map.sh'
'./check-linuxcnc-wasm-blockers-source-map.sh'
)
for guarded_entry in test-native.sh test-linuxcnc-source-link.sh build-wasm.sh; do
for source_map_guard in "${source_map_guards[@]}"; do
grep -F "$source_map_guard" "$guarded_entry" >/dev/null
done
done
grep -F 'manifest_core_count=$(count_nonempty_lines "$manifest_core_output")' build-wasm.sh >/dev/null
grep -F 'manifest_blocked_count=$(count_nonempty_lines "$manifest_blocked_output")' build-wasm.sh >/dev/null
grep -F 'manifest_core_count" -ne 26 || "$manifest_blocked_count" -ne 9' build-wasm.sh >/dev/null
grep -F 'run_parallel_wasm_probe "LinuxCNC wasm tooldata shim link probe" ./test-linuxcnc-wasm-tooldata-link.sh' build-wasm.sh >/dev/null
grep -F 'run_parallel_wasm_probe "LinuxCNC wasm rs274ngc_pre object probe" ./test-linuxcnc-wasm-rs274ngc-pre-object.sh' build-wasm.sh >/dev/null
grep -F 'wait_parallel_wasm_probes' build-wasm.sh >/dev/null
grep -F 'for required_tool in cmake emcmake emcc node; do' build-wasm.sh >/dev/null
grep -F 'CONFIGURE_RULE_VERSION=2' build-wasm.sh >/dev/null
grep -F -- '-DCNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON' build-wasm.sh >/dev/null
grep -F -- '-DCNC_SIM_ENABLE_SMOKE_BACKEND=OFF' build-wasm.sh >/dev/null
grep -F 'cmake --build build/wasm --target cnc_sim_wasm_runtime_probe cnc_sim_wasm --parallel "$build_jobs"' build-wasm.sh >/dev/null
grep -F 'cmp -s build/wasm/cnc_sim.js web/public/cnc_sim.js' build-wasm.sh >/dev/null
grep -F 'cmp -s build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm' build-wasm.sh >/dev/null
grep -F './test-web-wasm-node-smoke.sh' build-wasm.sh >/dev/null
grep -F './test-web-wasm-browser-smoke.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this source-link test compiles the manifest-selected' \
test-linuxcnc-source-link.sh >/dev/null
grep -F 'BUILD_RULE_VERSION=1' test-linuxcnc-source-link.sh >/dev/null
grep -F 'make --output-sync=target -j"$build_jobs" -f "$source_link_makefile"' \
test-linuxcnc-source-link.sh >/dev/null
grep -F 'LinuxCNC source basis: this syntax probe checks the native RS274 manifest' \
test-linuxcnc-source-syntax.sh >/dev/null
grep -F 'LinuxCNC source basis: the wasm syntax probe compiles the' \
test-linuxcnc-wasm-source-syntax.sh >/dev/null
grep -F "printf 'CONFIGURE_RULE_VERSION=1\n'" \
test-linuxcnc-wasm-cmake-safe-probe.sh >/dev/null
grep -F 'cmake --build "$build_dir" --target linuxcnc_rs274_wasm_safe_probe cnc_sim_wasm_runtime_probe --parallel "$build_jobs"' \
test-linuxcnc-wasm-cmake-safe-probe.sh >/dev/null
if grep -F 'printf '\''BUILD_JOBS=%s\n'\'' "$build_jobs"' build-wasm.sh >/dev/null; then
echo "build-wasm configure signature must not depend on CNC_SIM_BUILD_JOBS" >&2
exit 1
fi
for hot_signature_script in \
build-linuxcnc-wasm-link-probe.sh \
build-linuxcnc-wasm-probe-objects.sh \
build-linuxcnc-wasm-standalone-probe.sh \
build-linuxcnc-native-bridge-objects.sh \
test-linuxcnc-api-native.sh \
test-linuxcnc-bridge-native.sh \
test-linuxcnc-rs274-native.sh \
test-linuxcnc-source-link.sh \
test-linuxcnc-source-objects.sh \
test-linuxcnc-source-syntax.sh \
test-native.sh; do
if grep -F "sha256sum $hot_signature_script" "$hot_signature_script" >/dev/null; then
echo "$hot_signature_script hot object signature must use BUILD_RULE_VERSION instead of the whole script hash" >&2
exit 1
fi
done
python3 - <<'PY'
from pathlib import Path
import subprocess
source_map = Path("docs/linuxcnc-build-source-map.md").read_text(encoding="utf-8")
wasm_manifest = Path("linuxcnc-rs274-wasm-source-files.txt")
core_count = len(subprocess.check_output(
["./list-linuxcnc-wasm-manifest-sources.sh", str(wasm_manifest), "core"],
text=True,
).splitlines())
blocked_count = len(subprocess.check_output(
["./list-linuxcnc-wasm-manifest-sources.sh", str(wasm_manifest), "blocked"],
text=True,
).splitlines())
if core_count != 26 or blocked_count != 9:
raise SystemExit(f"unexpected default WASM manifest partition: core={core_count} blocked={blocked_count}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"test-linuxcnc-source-syntax.sh",
"test-linuxcnc-wasm-cmake-safe-probe.sh",
"build-wasm.sh",
]:
if f"| `{entry}` |" not in source_map:
raise SystemExit(f"docs/linuxcnc-build-source-map.md missing build entry: {entry}")
for phrase in [
"must not add CNC",
"must not expand the temporary smoke parser",
"CNC_SIM_BUILD_JOBS",
"BUILD_RULE_VERSION",
"CONFIGURE_RULE_VERSION",
"CNC_SIM_ENABLE_SMOKE_BACKEND=OFF",
"Node smoke before browser smoke",
"does not interpret",
"G-code",
]:
if phrase not in source_map:
raise SystemExit(f"docs/linuxcnc-build-source-map.md missing boundary phrase: {phrase}")
build_wasm = Path("build-wasm.sh").read_text(encoding="utf-8").splitlines()
positions = {}
for index, line in enumerate(build_wasm, start=1):
if 'run_parallel_wasm_probe "LinuxCNC wasm tooldata shim link probe"' in line:
positions["parallel_start"] = index
if "wait_parallel_wasm_probes" in line and "parallel_start" in positions and "wait" not in positions:
positions["wait"] = index
if "LinuxCNC wasm rs274ngc_pre link blocker scan" in line:
positions["pre_link"] = index
if "for required_tool in cmake emcmake emcc node; do" in line:
positions["tools"] = index
if "emcmake cmake -S core -B build/wasm" in line:
positions["configure"] = index
if "cmake --build build/wasm --target cnc_sim_wasm_runtime_probe cnc_sim_wasm" in line:
positions["build"] = index
if "cp build/wasm/cnc_sim.js web/public/cnc_sim.js" in line:
positions["cp_js"] = index
if "cp build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm" in line:
positions["cp_wasm"] = index
if "cmp -s build/wasm/cnc_sim.js web/public/cnc_sim.js" in line:
positions["cmp_js"] = index
if "cmp -s build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm" in line:
positions["cmp_wasm"] = index
if "./test-web-wasm-node-smoke.sh" in line:
positions["node"] = index
if "./test-web-wasm-browser-smoke.sh" in line:
positions["browser"] = index
required = [
"parallel_start", "wait", "pre_link", "tools", "configure", "build",
"cp_js", "cp_wasm", "cmp_js", "cmp_wasm", "node", "browser",
]
missing = [name for name in required if name not in positions]
if missing:
raise SystemExit(f"build-wasm.sh missing ordered build anchors: {', '.join(missing)}")
if not (positions["parallel_start"] < positions["wait"] < positions["pre_link"]):
raise SystemExit("build-wasm.sh must wait for parallel probes before rs274ngc_pre link checks")
if not (positions["tools"] < positions["configure"] < positions["build"]):
raise SystemExit("build-wasm.sh must check tools, then configure, then build")
if not (positions["cp_js"] < positions["cmp_js"] < positions["node"] < positions["browser"]):
raise SystemExit("build-wasm.js artifact JS copy/compare or smoke ordering changed")
if not (positions["cp_wasm"] < positions["cmp_wasm"] < positions["node"] < positions["browser"]):
raise SystemExit("build-wasm.wasm artifact copy/compare or smoke ordering changed")
PY

View File

@@ -0,0 +1,97 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard keeps the kinematics source map aligned
# with linuxcnc-kinematics-source-files.txt, which enumerates LinuxCNC
# kinematics, posemath, switchkins INI/HAL/remap/tooldata/assets, and adjacent
# build metadata. It validates source coverage documentation only; it does not
# add CNC behavior, expand smoke parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F 'GENSERKINSSRCS := emc/kinematics/ugenserkins.c' \
"$linuxcnc_root/src/emc/kinematics/Submakefile" >/dev/null
grep -F 'extern int kinematicsInverse(const struct EmcPose * world,' \
"$linuxcnc_root/src/emc/kinematics/kinematics.h" >/dev/null
grep -F 'extern int kinematicsSwitch(int switchkins_type);' \
"$linuxcnc_root/src/emc/kinematics/kinematics.h" >/dev/null
grep -F 'motion.switchkins-type' \
"$linuxcnc_root/configs/sim/axis/vismach/5axis/bridgemill/5axis.ini" >/dev/null
grep -F '#<kinstype>' \
"$linuxcnc_root/configs/sim/axis/vismach/5axis/bridgemill/remap_subs/428remap.ngc" >/dev/null
python3 - <<'PY'
from pathlib import Path
import re
manifest = Path("linuxcnc-kinematics-source-files.txt").read_text(encoding="utf-8").splitlines()
source_map = Path("docs/linuxcnc-kinematics-source-map.md").read_text(encoding="utf-8")
expected_groups = [
"core",
"config",
"remap",
"component",
"generated",
"header",
"metadata",
"asset",
"tooldata",
]
counts = {group: 0 for group in expected_groups}
for line in manifest:
if not line or line.startswith("#"):
continue
group = line.split(":", 1)[0]
if group not in counts:
raise SystemExit(f"unexpected kinematics manifest group in source map guard: {group}")
counts[group] += 1
for group, count in counts.items():
pattern = rf"\| `{re.escape(group)}` \| {count} \|"
if not re.search(pattern, source_map):
raise SystemExit(
f"docs/linuxcnc-kinematics-source-map.md {group} count no longer matches linuxcnc-kinematics-source-files.txt"
)
for group in expected_groups:
if f"`{group}`" not in source_map:
raise SystemExit(
f"docs/linuxcnc-kinematics-source-map.md is missing kinematics manifest group {group}"
)
required_phrases = [
"LinuxCNC Source Anchors",
"must not add CNC behavior",
"must not",
"expand the temporary smoke parser",
"`src/emc/kinematics/Submakefile`",
"`src/emc/kinematics/kinematics.h`",
"motion.switchkins-type",
"`#<kinstype>` remap assignment",
"not browser API surface",
"OPFS-only",
"derive M428/M429/M430 cases from",
"RTCP and five-axis coverage",
"./check-linuxcnc-kinematics-source-map.sh",
"./check-linuxcnc-inputs.sh linuxcnc-kinematics-source-files.txt --require-kinematics-complete",
"./check-linuxcnc-switchkins-remap-table.sh linuxcnc-kinematics-source-files.txt",
"does not interpret G-code",
]
for phrase in required_phrases:
if phrase not in source_map:
raise SystemExit(
f"docs/linuxcnc-kinematics-source-map.md is missing source-coverage boundary phrase: {phrase}"
)
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-kinematics-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-kinematics-source-map.sh")
PY

182
check-linuxcnc-opfs-source-map.sh Executable file
View File

@@ -0,0 +1,182 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties browser OPFS parameter persistence to
# interp_internal.hh defaults and rs274ngc_pre.cc restore_parameters() /
# save_parameters() file semantics. It validates bridge policy only; it does not
# add CNC behavior, expand smoke parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F '#define RS274NGC_PARAMETER_FILE_NAME_DEFAULT "rs274ngc.var"' \
"$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F '#define RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX ".bak"' \
"$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F 'int Interp::restore_parameters(const char *filename)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'int Interp::open(const char *filename)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F '_setup.file_pointer = fopen(filename, "r");' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'read_text(command, _setup.file_pointer, _setup.linetext,' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'int Interp::save_parameters(const char *filename,' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'std::string tempfile = std::string(filename) + ".new";' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F '+ RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX;' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'unlink(bakfile.c_str());' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'if(link(filename, bakfile.c_str()) < 0)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'if(rename(tempfile.c_str(), filename) < 0)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'RS274NGC_PARAMETER_FILE_NAME_DEFAULT` as `rs274ngc.var`' \
docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F 'RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX` as `.bak`' \
docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '`Interp::restore_parameters()` for parameter-file reads' \
docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '`Interp::open()` by opening' \
docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '`fopen(filename, "r")`, then `Interp::_read()`' \
docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '`read_text()`' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '`Interp::save_parameters()` for `filename + ".new"`' \
docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F 'unlinks `filename + ".bak"`, links the' \
docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F 'must not add independent filesystem behavior or expand the temporary smoke parser' \
docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F 'parseFileWithParameterFile()' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F 'OPFS Coverage' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/src/wasm-core.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/src/wasm-core.d.ts` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/src/app.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/test-browser-wasm-smoke-opfs-basic-sections.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/test-browser-wasm-smoke-opfs-parameter-sections.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/test-browser-wasm-smoke-opfs-workspace-sections.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/test-browser-wasm-smoke-opfs-mirror-sections.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/test-browser-wasm-smoke-opfs-directory-sections.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/test-browser-wasm-smoke-opfs-policy-sections.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '| `web/test-browser-wasm-smoke-app-sections.js` |' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F 'const LINUXCNC_DEFAULT_PARAMETER_FILE = "rs274ngc.var";' web/src/wasm-core.js >/dev/null
grep -F 'async parseFile(path, dialect = "linuxcnc", options = {})' web/src/wasm-core.js >/dev/null
grep -F 'OPFS workspace is not available for program file parsing' web/src/wasm-core.js >/dev/null
grep -F 'const program = new TextDecoder().decode(await opfs.readFile(path));' web/src/wasm-core.js >/dev/null
grep -F 'async parseFileWithParameterFile(path, parameterPath, dialect = "linuxcnc", options = {})' \
web/src/wasm-core.js >/dev/null
grep -F 'OPFS workspace is not available for program and parameter file parsing' web/src/wasm-core.js >/dev/null
grep -F '`${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`' web/src/wasm-core.js >/dev/null
grep -F '`${LINUXCNC_DEFAULT_PARAMETER_FILE}.bak`' web/src/wasm-core.js >/dev/null
grep -F 'LinuxCNC source basis: rs274ngc_pre.cc restore_parameters() reads the' web/src/wasm-core.js >/dev/null
grep -F 'before replacing the main file and managing filename + ".bak".' web/src/wasm-core.js >/dev/null
grep -F 'app frame did not clean up LinuxCNC temporary parameter file in OPFS' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'OPFS cannot be disabled in browser contexts with OPFS support' web/src/wasm-core.js >/dev/null
grep -F 'OPFS path must stay inside the CNC workspace' web/src/wasm-core.js >/dev/null
grep -F 'OPFS mount point must stay inside the Emscripten filesystem' web/src/wasm-core.js >/dev/null
grep -F 'async persistDirectory(path)' web/src/wasm-core.js >/dev/null
grep -F 'async readDirectory(path)' web/src/wasm-core.js >/dev/null
grep -F 'export type WasmOpfsWorkspace = {' web/src/wasm-core.d.ts >/dev/null
grep -F 'readDirectory(path: string): Promise<string[]>;' web/src/wasm-core.d.ts >/dev/null
grep -F 'persistDirectory(path: string): Promise<string[]>;' web/src/wasm-core.d.ts >/dev/null
grep -F 'loadParameterFile(path: string): Promise<Uint8Array | null>;' web/src/wasm-core.d.ts >/dev/null
grep -F 'persistParameterFile(path: string): Promise<Uint8Array>;' web/src/wasm-core.d.ts >/dev/null
grep -F 'opfs?: false | {' web/src/wasm-core.d.ts >/dev/null
grep -F 'mountPoint?: string;' web/src/wasm-core.d.ts >/dev/null
grep -F 'workspacePath?: string;' web/src/wasm-core.d.ts >/dev/null
grep -F 'const events = await simulator.parseFileWithParameterFile(programPath, parameterPath, "linuxcnc", parseOptions);' \
web/src/app.js >/dev/null
grep -F 'OPFS workspace is required for browser program parsing' web/src/app.js >/dev/null
grep -F 'runBrowserOpfsBasicSections(context, simulator)' \
web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'runBrowserOpfsParameterSections(context, simulator)' \
web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'runBrowserOpfsMirrorSections(context, simulator)' \
web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'runBrowserOpfsDirectorySections(context, simulator)' \
web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'runBrowserOpfsPolicySections(context)' web/test-browser-wasm-smoke-sections.js >/dev/null
grep -F 'runSection("opfs directory persist and load"' \
web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'runSection("opfs metadata queries avoid wasm mirror pollution"' \
web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'runSection("opfs copy and move mirror files"' \
web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
grep -F 'runSection("browser opfs policy"' \
web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
grep -F 'runSection("opfs parameter restore in new instance"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("opfs invalid parameter restore failures"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("opfs stale parameter state cleanup"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("opfs program parse and restore"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("browser app opfs restore"' test-web-wasm-browser-smoke.sh >/dev/null
if grep -R -n -E 'localStorage|sessionStorage|indexedDB|showOpenFilePicker|showSaveFilePicker|showDirectoryPicker|webkitRequestFileSystem|FileReader' web/src; then
echo "browser app/wasm code must not add filesystem persistence outside OPFS" >&2
exit 1
fi
if grep -n -E 'localStorage|sessionStorage|indexedDB|showOpenFilePicker|showSaveFilePicker|showDirectoryPicker|webkitRequestFileSystem|FileReader' \
web/test-browser-wasm-smoke-*.js \
web/test-browser-wasm-smoke.html \
test-web-wasm-browser-smoke.sh; then
echo "browser OPFS smoke harness must not add filesystem persistence outside OPFS" >&2
exit 1
fi
python3 - <<'PY'
from pathlib import Path
source_map = Path("docs/linuxcnc-opfs-source-map.md").read_text(encoding="utf-8")
coverage_entries = [
"web/src/wasm-core.js",
"web/src/wasm-core.d.ts",
"web/src/app.js",
"web/test-browser-wasm-smoke-opfs-basic-sections.js",
"web/test-browser-wasm-smoke-opfs-parameter-sections.js",
"web/test-browser-wasm-smoke-opfs-workspace-sections.js",
"web/test-browser-wasm-smoke-opfs-mirror-sections.js",
"web/test-browser-wasm-smoke-opfs-directory-sections.js",
"web/test-browser-wasm-smoke-opfs-policy-sections.js",
"web/test-browser-wasm-smoke-app-sections.js",
]
for entry in coverage_entries:
if f"| `{entry}` |" not in source_map:
raise SystemExit(f"docs/linuxcnc-opfs-source-map.md missing OPFS coverage entry: {entry}")
required_phrases = [
"programs/current.ngc",
"parameters/rs274ngc.var",
'fopen(filename, "r")',
"LinuxCNC `read_text()`",
"program reads and parameter restore/save bridge files under the OPFS workspace",
"`.new`, `.bak`, failure, and stale-state semantics",
'unlinks `filename + ".bak"`',
"OPFS/WASM mirror persistence",
"OPFS directory persistence and WASM mirror refresh",
"OPFS workspace and mount policy",
"Browser OPFS smoke harness files must also stay free of Local Storage",
"they may only verify OPFS-backed browser persistence",
"OPFS-backed workspace, directory, parameter-file, and mount/workspace option types",
"app-level OPFS program, parameter, backup, and reload behavior",
"./check-linuxcnc-opfs-source-map.sh",
"does not interpret G-code",
]
for phrase in required_phrases:
if phrase not in source_map:
raise SystemExit(f"docs/linuxcnc-opfs-source-map.md missing OPFS coverage phrase: {phrase}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-opfs-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-opfs-source-map.sh")
PY

View File

@@ -0,0 +1,102 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard keeps the local source map aligned with the
# LinuxCNC-root relative rs274 manifests that drive native and WASM source-link
# probes. It validates documentation/manifest drift only; it does not add CNC
# behavior, expand smoke parsing, or interpret G-code.
python3 - <<'PY'
from pathlib import Path
source_map = Path("docs/linuxcnc-rs274-source-map.md").read_text(encoding="utf-8")
native_manifest = Path("linuxcnc-rs274-source-files.txt").read_text(encoding="utf-8").splitlines()
wasm_manifest = Path("linuxcnc-rs274-wasm-source-files.txt").read_text(encoding="utf-8").splitlines()
expected = {
"### Interpreter core": [
Path(line.split(":", 2)[1]).name
for line in native_manifest
if line.startswith("core:")
],
"### Python binding modules": [
Path(line.split(":", 2)[1]).name
for line in native_manifest
if line.startswith("binding:")
],
"### WASM-safe source core": [
line.split(":", 2)[1]
for line in wasm_manifest
if line.startswith("core:")
],
"### WASM-blocked sources": [
line.split(":", 2)[1]
for line in wasm_manifest
if line.startswith("blocked:")
],
"### WASM-tracked headers": [
line.split(":", 2)[1]
for line in wasm_manifest
if line.startswith("header:")
],
"### WASM-blocked headers": [
line.split(":", 2)[1]
for line in wasm_manifest
if line.startswith("blocked-header:")
],
"### WASM metadata sources": [
line.split(":", 2)[1]
for line in wasm_manifest
if line.startswith("metadata:")
],
}
actual = {section: [] for section in expected}
active_section = None
for line in source_map.splitlines():
if line in expected:
active_section = line
continue
if active_section and line.startswith("### "):
active_section = None
if active_section and line.startswith("- `"):
actual[active_section].append(line.split("`", 2)[1])
labels = {
"### Interpreter core": "interpreter core",
"### Python binding modules": "Python binding module",
"### WASM-safe source core": "WASM-safe source core",
"### WASM-blocked sources": "WASM-blocked source",
"### WASM-tracked headers": "WASM-tracked header",
"### WASM-blocked headers": "WASM-blocked header",
"### WASM metadata sources": "WASM metadata source",
}
for section, expected_entries in expected.items():
if actual[section] != expected_entries:
raise SystemExit(
f"docs/linuxcnc-rs274-source-map.md {labels[section]} list no longer matches source manifest"
)
for phrase in [
"must not add CNC behavior",
"must not expand the temporary smoke parser",
"source coverage only",
"must not become browser API fields",
"temporary smoke parser fallback behavior",
"Browser-hostile dependencies must remain tracked blockers",
"./check-linuxcnc-rs274-source-map.sh",
"does not interpret G-code",
]:
if phrase not in source_map:
raise SystemExit(f"docs/linuxcnc-rs274-source-map.md missing boundary phrase: {phrase}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-rs274-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-rs274-source-map.sh")
PY

View File

@@ -0,0 +1,107 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties generated switchkins/remap aliases and
# config cases to LinuxCNC configs/sim INI REMAP entries, remap_subs
# #<kinstype> assignments, and halcmd loadusr parsing sources. It validates
# generator/table policy only; it does not add CNC behavior, expand smoke
# parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F 'LinuxCNC source basis: generated switchkins/remap tables are extracted from' \
generate-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'src/hal/utils/halcmd_commands.cc do_loadusr_cmd()' \
generate-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'getopt("+wWin:")' generate-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'src/hal/utils/halcmd_completion.c lists the common -W/-Wn/-w/-iw forms' \
generate-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'LinuxCNC TOOL_TABLE source is not listed as tooldata' \
generate-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'LinuxCNC source basis: switchkins/remap table validation derives M428/M429/M430' \
check-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'validate_tool_table_source_coverage_only' \
check-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'exposes TOOL_TABLE through switchkins API/web bridge' \
check-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'int do_loadusr_cmd(const char *args[])' "$linuxcnc_root/src/hal/utils/halcmd_commands.cc" >/dev/null
grep -F 'getopt(argc,' "$linuxcnc_root/src/hal/utils/halcmd_commands.cc" >/dev/null
grep -F '"+wWin:"' "$linuxcnc_root/src/hal/utils/halcmd_commands.cc" >/dev/null
grep -F -- '-Wn' "$linuxcnc_root/src/hal/utils/halcmd_completion.c" >/dev/null
python3 - "$linuxcnc_root" <<'PY'
from pathlib import Path
import json
import re
import sys
linuxcnc_root = Path(sys.argv[1])
source_map = Path("docs/linuxcnc-switchkins-remap-source-map.md").read_text(encoding="utf-8")
table = Path("core/src/linuxcnc_switchkins_remap_table.inc").read_text(encoding="utf-8")
cpp_cases = Path("core/tests/linuxcnc_switchkins_remap_config_cases.inc").read_text(encoding="utf-8")
json_cases = json.loads(Path("web/public/linuxcnc_switchkins_remap_config_cases.json").read_text(encoding="utf-8"))
counts = {
"core/src/linuxcnc_switchkins_remap_table.inc": len(re.findall(r'^\s*\{"', table, re.M)),
"core/tests/linuxcnc_switchkins_remap_config_cases.inc": len(re.findall(r'^\s*\{"', cpp_cases, re.M)),
"web/public/linuxcnc_switchkins_remap_config_cases.json": len(json_cases),
}
for path, count in counts.items():
if f"| `{path}` | {count} |" not in source_map:
raise SystemExit(f"docs/linuxcnc-switchkins-remap-source-map.md count mismatch for {path}")
remap_sources = [
path
for path in (linuxcnc_root / "configs/sim").rglob("*remap.ngc")
if path.name in {"428remap.ngc", "429remap.ngc", "430remap.ngc"}
]
remap_ini_re = re.compile(r"REMAP\s*=\s*M(428|429|430)(?:\D|$)", re.I)
remap_inis = [
path
for path in (linuxcnc_root / "configs/sim").rglob("*.ini")
if remap_ini_re.search(path.read_text(encoding="utf-8", errors="ignore"))
]
if f"| LinuxCNC M428/M429/M430 remap sources | {len(remap_sources)} |" not in source_map:
raise SystemExit("docs/linuxcnc-switchkins-remap-source-map.md remap source count mismatch")
if f"| LinuxCNC switchkins REMAP INI sources | {len(remap_inis)} |" not in source_map:
raise SystemExit("docs/linuxcnc-switchkins-remap-source-map.md REMAP INI source count mismatch")
required_phrases = [
"must not add CNC behavior",
"must not expand the temporary smoke parser",
"`#<kinstype>` assignments",
"[EMCIO] TOOL_TABLE` entries are source coverage only",
"must not become generated switchkins aliases",
"./check-linuxcnc-switchkins-remap-source-map.sh",
"./check-linuxcnc-switchkins-remap-table.sh linuxcnc-kinematics-source-files.txt",
"does not interpret G-code",
]
for phrase in required_phrases:
if phrase not in source_map:
raise SystemExit(
f"docs/linuxcnc-switchkins-remap-source-map.md is missing boundary phrase: {phrase}"
)
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-switchkins-remap-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-switchkins-remap-source-map.sh")
case_fields = {case["field"] for case in json_cases}
source_only_field_re = re.compile(r"asset|metadata|tool|tooldata|tool_table|TOOL_TABLE", re.I)
bad_fields = [field for field in case_fields if source_only_field_re.search(field)]
if bad_fields:
raise SystemExit(f"generated switchkins config cases expose source-only fields: {bad_fields}")
api_surface_re = re.compile(r'\b(?:asset|metadata|toolData|tooldata|toolTable|tooltable|tool_table|TOOL_TABLE)\b')
for api_path in ["web/src/wasm-core.js", "web/src/index.ts", "core/include/cnc_sim_api.h"]:
if api_surface_re.search(Path(api_path).read_text(encoding="utf-8")):
raise SystemExit(f"{api_path} exposes source-only switchkins inputs through API surface")
PY

View File

@@ -381,6 +381,7 @@ validate_generator_cli_contract() {
local cli_manifest_without_remap
local cli_manifest_without_tooldata
local cli_output_root_log
local cli_output_file_log
local cli_output_whitespace_log
cli_dir=$(mktemp -d "${TMPDIR:-/tmp}/linuxcnc_switchkins_remap_cli.XXXXXX")
trap 'rm -rf "$cli_dir"' EXIT
@@ -411,6 +412,7 @@ validate_generator_cli_contract() {
cli_manifest_without_remap="$cli_dir/bridgemill-manifest-without-remap.txt"
cli_manifest_without_tooldata="$cli_dir/bridgemill-manifest-without-tooldata.txt"
cli_output_root_log="$cli_dir/output-root.log"
cli_output_file_log="$cli_dir/output-file.log"
cli_output_whitespace_log="$cli_dir/output-whitespace.log"
if ! awk -F: -v manifest="$manifest_file" '
@@ -769,6 +771,18 @@ validate_generator_cli_contract() {
sed -n '1,20p' "$cli_output_root_log" >&2
exit 1
fi
printf 'not a directory\n' >"$cli_dir/output-file"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
--all-output-dir "$cli_dir/output-file" "$cli_manifest" >"$cli_output_file_log" 2>&1; then
echo "switchkins remap generator accepted a file as output directory" >&2
exit 1
fi
if ! grep -F "switchkins remap output path is not a directory: $cli_dir/output-file" \
"$cli_output_file_log" >/dev/null; then
echo "switchkins remap generator did not report file output paths clearly" >&2
sed -n '1,20p' "$cli_output_file_log" >&2
exit 1
fi
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh --all-output-dir "$cli_dir/out" "$cli_manifest"
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh "$cli_manifest" \

View File

@@ -0,0 +1,333 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties LinuxCNC tooldata/Submakefile,
# src/emc/tooldata sources, native/WASM manifests, and wasm-safe tooldata shims
# to the tooldata source map. It validates source coverage only; it does not
# add CNC behavior, expand smoke parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_nml.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_mmap.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_common.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_db.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'TOOL_MMAP_READ_SRCS = emc/tooldata/tool_mmap_read.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'TOOL_WATCH_SRCS = emc/tooldata/tool_watch.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'int main (int argc,char**argv)' \
"$linuxcnc_root/src/emc/tooldata/tool_mmap_read.cc" >/dev/null
grep -F 'if (tool_mmap_user())' \
"$linuxcnc_root/src/emc/tooldata/tool_mmap_read.cc" >/dev/null
grep -F 'idx_last = tooldata_last_index_get();' \
"$linuxcnc_root/src/emc/tooldata/tool_mmap_read.cc" >/dev/null
grep -F 'if (tooldata_get(&tdata,idx) != IDX_OK)' \
"$linuxcnc_root/src/emc/tooldata/tool_mmap_read.cc" >/dev/null
grep -F 'RCS_STAT_CHANNEL *stat = 0;' \
"$linuxcnc_root/src/emc/tooldata/tool_watch.cc" >/dev/null
grep -F 'stat = new RCS_STAT_CHANNEL(emcFormat, "emcStatus", "xemc", nmlfile);' \
"$linuxcnc_root/src/emc/tooldata/tool_watch.cc" >/dev/null
grep -F 'EMC_STAT *emcStatus = static_cast<EMC_STAT*>(stat->get_address());' \
"$linuxcnc_root/src/emc/tooldata/tool_watch.cc" >/dev/null
grep -F 'emcStatus->io.tool.pocketPrepped' \
"$linuxcnc_root/src/emc/tooldata/tool_watch.cc" >/dev/null
grep -F '#define TOOL_MMAP_FILENAME ".tool.mmap"' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'bool tool_mmap_is_random_toolchanger(void)' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'ans = hptr->is_random_toolchanger;' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'int tool_mmap_creator(EMC_TOOL_STAT const * ptr,int random_toolchanger)' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'static int inited=0;' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'if (inited) {' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'inited = 1;' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'hptr->is_random_toolchanger = random_toolchanger;' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'hptr->last_index = 0;' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'int tool_mmap_user()' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'fprintf(stderr,"tool_mmap_user(): tool mmap not available\n");' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'return(-1);' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'void tool_mmap_close()' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'if (!tool_mmap_base) { return; }' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'int tooldata_last_index_get(void)' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'tool_mmap_mutex_give(); return -1;' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'int tooldata_find_index_for_tool(int toolno)' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'if (toolno == -1) {tool_mmap_mutex_give(); return -1;}' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'if (!hptr->is_random_toolchanger && toolno == 0)' \
"$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'int tool_nml_register(CANON_TOOL_TABLE *tblptr)' \
"$linuxcnc_root/src/emc/tooldata/tooldata_nml.cc" >/dev/null
grep -F 'int tooldata_db_init(char progname_plus_args[]' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F '#define MAX_DB_PROGRAM_ARGS 10' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'char* token = strtok_r(progname_plus_args, " ", &saveptr);' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'if (child_argc >= MAX_DB_PROGRAM_ARGS)' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'if (access(child_argv[0],X_OK))' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'int tooldata_db_getall() {' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'if (!db_live) {return -1;}' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'int tooldata_db_notify(tool_notify_t ntype,' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'if (!db_live) return 0;' \
"$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'int tooldata_read_entry(const char *input_line)' \
"$linuxcnc_root/src/emc/tooldata/tooldata_common.cc" >/dev/null
grep -F 'LinuxCNC tooldata source map' docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F 'must not add CNC behavior' docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F 'must not expand the temporary smoke parser' docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F '`src/emc/tooldata/Submakefile` selects `tooldata_mmap.cc`' \
docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F 'Tool table files referenced by switchkins INI files are source coverage only' \
docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F '`src/emc/tooldata/tool_mmap_read.cc` is the mmap tool table reader utility' \
docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F '`tool_mmap_user()`, `tooldata_last_index_get()`, and `tooldata_get()`' \
docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F "single-creator process boundary from" docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F "including after" docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F '`src/emc/tooldata/tool_watch.cc` is the NML/mmap tool table watcher utility' \
docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F '`EMC_STAT::io.tool` fields through `RCS_STAT_CHANNEL`' \
docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F 'tooldata:src/emc/tooldata/tooldata_common.cc:shared tool table parser/format logic' \
linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'tooldata:src/emc/tooldata/tooldata_mmap.cc:native mmap implementation; replace for wasm' \
linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'tooldata:src/emc/tooldata/tooldata_db.cc:external database integration; exclude from wasm core' \
linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'tooldata:src/emc/tooldata/tooldata_nml.cc:NML tool table backend selected by LinuxCNC TOOL_NML_FLAG' \
linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'header:src/emc/tooldata/tooldata.hh:LinuxCNC tooldata declarations used by RS274 and wasm-safe tooldata shims' \
linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'metadata:src/emc/tooldata/Submakefile:LinuxCNC tooldata build metadata defining mmap and NML backend source selection' \
linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'core:src/emc/tooldata/tooldata_common.cc:shared tool table parser/format logic linked with wasm-safe mmap backend' \
linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'blocked:src/emc/tooldata/tooldata_mmap.cc:native mmap implementation' \
linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'metadata:src/emc/tooldata/tooldata_db.cc:source basis for wasm-safe tooldata runtime export shim' \
linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'metadata:src/emc/tooldata/tooldata_nml.cc:source basis for wasm-safe tooldata runtime NML export shim' \
linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'LinuxCNC source basis: src/emc/tooldata/tooldata_mmap.cc provides these' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'bool mmap_created = false;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'bool mmap_random_toolchanger = false;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'bool mmap_creator_called = false;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'int tool_mmap_creator(EMC_TOOL_STAT const *ptr, int random_toolchanger)' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'Error: tool_mmap_creator already called BYE' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'mmap_created = true;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'mmap_random_toolchanger = random_toolchanger;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'last_index = 0;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'int tool_mmap_user(void)' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'tool_mmap_user(): tool mmap not available' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'void tool_mmap_close(void)' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'mmap_created = false;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'bool tool_mmap_is_random_toolchanger(void)' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'return mmap_random_toolchanger;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'int tooldata_last_index_get(void)' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'return -1;' \
core/wasm_shims/tooldata/tooldata_mmap_backend.cc >/dev/null
grep -F 'LinuxCNC source basis: src/emc/tooldata/tooldata_nml.cc and tooldata_db.cc' \
core/wasm_shims/tooldata/tooldata_runtime_stubs.cc >/dev/null
grep -F 'constexpr int MAX_DB_PROGRAM_ARGS = 10;' \
core/wasm_shims/tooldata/tooldata_runtime_stubs.cc >/dev/null
grep -F 'strtok_r(progname_plus_args, " ", &saveptr)' \
core/wasm_shims/tooldata/tooldata_runtime_stubs.cc >/dev/null
grep -F 'argc exceeds MAX_DB_PROGRAM_ARGS=%d' \
core/wasm_shims/tooldata/tooldata_runtime_stubs.cc >/dev/null
grep -F 'access(program, X_OK) != 0' \
core/wasm_shims/tooldata/tooldata_runtime_stubs.cc >/dev/null
grep -F 'returns 0 when' core/wasm_shims/tooldata/tooldata_runtime_stubs.cc >/dev/null
grep -F 'LinuxCNC source basis: src/emc/tooldata/tooldata_common.cc provides' \
core/wasm_shims/tooldata/tooldata_common_probe_main.cc >/dev/null
grep -F 'LinuxCNC source basis: src/emc/tooldata/tooldata_mmap.cc, tooldata_nml.cc,' \
core/wasm_shims/tooldata/tooldata_probe_main.cc >/dev/null
grep -F 'tooldata_db_notify() returns 0 without side effects while db_live is' \
core/wasm_shims/tooldata/tooldata_probe_main.cc >/dev/null
grep -F 'tooldata_db_init(missing_db_program_with_arg.data(), 1) != -1' \
core/wasm_shims/tooldata/tooldata_probe_main.cc >/dev/null
grep -F 'missing_db_program_with_arg[separator] != 0' \
core/wasm_shims/tooldata/tooldata_probe_main.cc >/dev/null
grep -F 'tooldata_db_init(too_many_db_args, 1) != -1' \
core/wasm_shims/tooldata/tooldata_probe_main.cc >/dev/null
grep -F 'tooldata_db_init(non_db_program, 1) != -1' \
core/wasm_shims/tooldata/tooldata_probe_main.cc >/dev/null
grep -F 'tool_mmap_creator(nullptr, 1);' \
core/wasm_shims/tooldata/tooldata_probe_main.cc >/dev/null
grep -F 'WEXITSTATUS(recreate_status) == 0' \
core/wasm_shims/tooldata/tooldata_probe_main.cc >/dev/null
grep -F 'core/wasm_shims/tooldata/tooldata_mmap_backend.cc' docs/linuxcnc-wasm-shims-source-map.md >/dev/null
grep -F 'core/wasm_shims/tooldata/tooldata_runtime_stubs.cc' docs/linuxcnc-wasm-shims-source-map.md >/dev/null
grep -F 'LinuxCNC source basis: this link probe runs the wasm-safe tooldata shim' \
test-linuxcnc-wasm-tooldata-link.sh >/dev/null
grep -F 'LinuxCNC source basis: this link probe runs tooldata_common through the' \
test-linuxcnc-wasm-tooldata-common-link.sh >/dev/null
grep -F 'LinuxCNC source basis: this symbol probe compares the wasm mmap backend shim' \
test-linuxcnc-wasm-tooldata-mmap-symbols.sh >/dev/null
grep -F 'LinuxCNC source basis: this probe compares wasm-safe tooldata runtime stubs' \
test-linuxcnc-wasm-tooldata-runtime-symbols.sh >/dev/null
grep -F '!!!!db_init: argc exceeds MAX_DB_PROGRAM_ARGS=10' \
test-linuxcnc-wasm-tooldata-link.sh >/dev/null
grep -F 'not executable' test-linuxcnc-wasm-tooldata-link.sh >/dev/null
grep -F 'Error: tool_mmap_creator already called BYE' \
test-linuxcnc-wasm-tooldata-link.sh >/dev/null
grep -F 'run_parallel_wasm_probe "LinuxCNC wasm tooldata shim link probe" ./test-linuxcnc-wasm-tooldata-link.sh' \
build-wasm.sh >/dev/null
grep -F 'run_parallel_wasm_probe "LinuxCNC wasm tooldata_common link probe" ./test-linuxcnc-wasm-tooldata-common-link.sh' \
build-wasm.sh >/dev/null
grep -F 'run_parallel_wasm_probe "LinuxCNC wasm tooldata_mmap symbol probe" ./test-linuxcnc-wasm-tooldata-mmap-symbols.sh' \
build-wasm.sh >/dev/null
grep -F 'run_parallel_wasm_probe "LinuxCNC wasm tooldata runtime symbol probe" ./test-linuxcnc-wasm-tooldata-runtime-symbols.sh' \
build-wasm.sh >/dev/null
python3 - <<'PY'
from pathlib import Path
import subprocess
doc = Path("docs/linuxcnc-tooldata-source-map.md").read_text(encoding="utf-8")
build_wasm_lines = Path("build-wasm.sh").read_text(encoding="utf-8").splitlines()
native_tooldata = subprocess.check_output(
["./list-linuxcnc-source-manifest-sources.sh", "linuxcnc-rs274-source-files.txt", "tooldata"],
text=True,
).splitlines()
expected_native = [
"src/emc/tooldata/tooldata_common.cc",
"src/emc/tooldata/tooldata_mmap.cc",
"src/emc/tooldata/tooldata_db.cc",
"src/emc/tooldata/tooldata_nml.cc",
"src/emc/tooldata/tool_mmap_read.cc",
"src/emc/tooldata/tool_watch.cc",
]
for source in expected_native:
if source not in native_tooldata:
raise SystemExit(f"native RS274 manifest missing LinuxCNC tooldata source: {source}")
kin_tooldata = subprocess.check_output(
["./list-linuxcnc-kinematics-manifest-sources.sh", "linuxcnc-kinematics-source-files.txt", "tooldata"],
text=True,
).splitlines()
if len([source for source in kin_tooldata if source.endswith(".tbl")]) != 10:
raise SystemExit("kinematics manifest must track exactly 10 LinuxCNC TOOL_TABLE sources")
positions = {}
for index, line in enumerate(build_wasm_lines, start=1):
if 'run_parallel_wasm_probe "LinuxCNC wasm tooldata shim link probe"' in line:
positions["tooldata_link"] = index
if 'run_parallel_wasm_probe "LinuxCNC wasm tooldata_common link probe"' in line:
positions["tooldata_common"] = index
if 'run_parallel_wasm_probe "LinuxCNC wasm tooldata_mmap symbol probe"' in line:
positions["tooldata_mmap"] = index
if 'run_parallel_wasm_probe "LinuxCNC wasm tooldata runtime symbol probe"' in line:
positions["tooldata_runtime"] = index
if "cp build/wasm/cnc_sim.js web/public/cnc_sim.js" in line:
positions["copy_js"] = index
if "cp build/wasm/cnc_sim.wasm web/public/cnc_sim.wasm" in line:
positions["copy_wasm"] = index
missing = [
name for name in (
"tooldata_link",
"tooldata_common",
"tooldata_mmap",
"tooldata_runtime",
"copy_js",
"copy_wasm",
)
if name not in positions
]
if missing:
raise SystemExit(f"build-wasm.sh missing tooldata/copy ordering anchor: {', '.join(missing)}")
for probe in ("tooldata_link", "tooldata_common", "tooldata_mmap", "tooldata_runtime"):
if not (positions[probe] < positions["copy_js"] and positions[probe] < positions["copy_wasm"]):
raise SystemExit("build-wasm.sh must run all tooldata link/symbol probes before copying browser artifacts")
for path in [
"core/wasm_shims/tooldata/tooldata_mmap_backend.cc",
"core/wasm_shims/tooldata/tooldata_runtime_stubs.cc",
"test-linuxcnc-wasm-tooldata-link.sh",
"test-linuxcnc-wasm-tooldata-common-link.sh",
"test-linuxcnc-wasm-tooldata-mmap-symbols.sh",
"test-linuxcnc-wasm-tooldata-runtime-symbols.sh",
]:
if path not in doc:
raise SystemExit(f"docs/linuxcnc-tooldata-source-map.md missing coverage path: {path}")
for phrase in [
"must not add CNC behavior",
"must not expand the temporary smoke parser",
"source coverage only",
"must not become browser API fields",
"WASM-safe tooldata shims must preserve the LinuxCNC export surface",
"must keep external DB services unavailable",
"must not fork, exec, poll",
"MAX_DB_PROGRAM_ARGS=10",
"`strtok_r()` argument splitting",
"access(..., X_OK)",
"mmap tool table reader utility",
"NML/mmap tool table watcher utility",
"mmap creator/user/close lifecycle",
"random-toolchanger flag",
"last-index unavailable return",
"in-memory replacement for native mmap",
"inactive-DB return values",
"./check-linuxcnc-tooldata-source-map.sh",
"does not interpret G-code",
]:
if phrase not in doc:
raise SystemExit(f"docs/linuxcnc-tooldata-source-map.md missing boundary phrase: {phrase}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-tooldata-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-tooldata-source-map.sh")
PY

View File

@@ -0,0 +1,190 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties the documented WASM blocker map to
# LinuxCNC RS274/Python/remap/tooldata sources, the rs274 WASM manifest, and
# wasm-safe shim replacement sources. It validates source coverage only; it
# does not add CNC behavior, expand smoke parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest"
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F 'LinuxCNC source basis: this blocker scan is constrained to the RS274/WASM' \
analyze-linuxcnc-wasm-blockers.sh >/dev/null
grep -F 'blocked replacement is not in wasm shim build set' \
analyze-linuxcnc-wasm-blockers.sh >/dev/null
grep -F 'shim_source_output=$(./list-linuxcnc-wasm-safe-shims.sh)' \
analyze-linuxcnc-wasm-blockers.sh >/dev/null
grep -F 'LinuxCNC source basis: this guardrail locks the blocker categories reported' \
test-linuxcnc-wasm-blockers.sh >/dev/null
grep -F 'Cache wasm blocker analyzer output' docs/linuxcnc-source-policy.md >/dev/null
grep -F '#include <Python.h>' "$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyInit_gcode' "$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F '#include <boost/python/module.hpp>' "$linuxcnc_root/src/emc/rs274ngc/interpmodule.cc" >/dev/null
grep -F '#include "pythonplugin/python_plugin.hh"' "$linuxcnc_root/src/emc/rs274ngc/interp_python.cc" >/dev/null
grep -F 'dlopen' "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F 'opendir(direct)' "$linuxcnc_root/src/emc/rs274ngc/interp_o_word.cc" >/dev/null
grep -F 'readdir(aDir)' "$linuxcnc_root/src/emc/rs274ngc/interp_o_word.cc" >/dev/null
grep -F 'std::string tempfile = std::string(filename) + ".new";' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F '+ RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX;' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'unlink(bakfile.c_str());' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'if(link(filename, bakfile.c_str()) < 0)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'if(rename(tempfile.c_str(), filename) < 0)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F '#define TOOL_MMAP_FILENAME ".tool.mmap"' "$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'int tool_mmap_creator(' "$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
python3 - "$linuxcnc_root" "$manifest" <<'PY'
from pathlib import Path
import re
import subprocess
import sys
linuxcnc_root = Path(sys.argv[1])
manifest = Path(sys.argv[2])
source_map = Path("docs/linuxcnc-wasm-blockers-source-map.md").read_text(encoding="utf-8")
manifest_lines = [
line for line in manifest.read_text(encoding="utf-8").splitlines()
if line and not line.startswith("#")
]
manifest_counts = {"core": 0, "blocked": 0, "header": 0, "blocked-header": 0, "metadata": 0}
for line in manifest_lines:
group = line.split(":", 1)[0]
if group not in manifest_counts:
raise SystemExit(f"unexpected WASM manifest group in blocker source map guard: {group}")
manifest_counts[group] += 1
for group, count in manifest_counts.items():
if f"| `{group}` | {count} |" not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-blockers-source-map.md count mismatch for {group}")
output = subprocess.check_output(
["./analyze-linuxcnc-wasm-blockers.sh", str(manifest)],
text=True,
env={"LINUXCNC_ROOT": str(linuxcnc_root)},
)
sections = {}
current = None
for line in output.splitlines():
if line.startswith("[") and line.endswith("]"):
current = line[1:-1]
sections[current] = []
elif current and line:
sections[current].append(line)
expected_counts = {
"manifest-core": "core=26",
"manifest-blocked": "blocked=9",
"python": 8,
"tooldata": 1,
"native-backend": 1,
"native-fs": 1,
"blocked-replacements": 9,
"core-python-shimmed": 6,
"core-dlopen-shimmed": 1,
"core-tooldata-shimmed": 1,
"core-tooldata-users-shimmed": 2,
"core-native-fs-shimmed": 16,
}
manifest_section = sections.get("manifest", [])
if expected_counts["manifest-core"] not in manifest_section:
raise SystemExit("WASM blocker analyzer core count changed")
if expected_counts["manifest-blocked"] not in manifest_section:
raise SystemExit("WASM blocker analyzer blocked count changed")
for section, count in expected_counts.items():
if section.startswith("manifest-"):
continue
entries = [entry for entry in sections.get(section, []) if entry != "(none)"]
if len(entries) != count:
raise SystemExit(f"WASM blocker analyzer section {section} count changed: {len(entries)}")
if f"| `{section}` | {count} |" not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-blockers-source-map.md analyzer count mismatch for {section}")
if sections.get("dlopen") != ["(none)"]:
raise SystemExit("blocked dlopen section is no longer empty")
if sections.get("tooldata-users") != ["(none)"]:
raise SystemExit("blocked tooldata-users section is no longer empty")
shim_paths = set(Path(line).as_posix() for line in subprocess.check_output(
["./list-linuxcnc-wasm-safe-shims.sh"],
text=True,
).splitlines() if line)
required_tracked_entries = [
"src/emc/rs274ngc/interp_base.cc -> core/wasm_shims/dlfcn.cc",
"src/emc/rs274ngc/gcodemodule.cc",
"src/emc/rs274ngc/interpmodule.cc",
"src/emc/tooldata/tooldata_mmap.cc",
"src/emc/rs274ngc/interp_base.cc",
"src/emc/rs274ngc/interp_o_word.cc",
"src/emc/rs274ngc/rs274ngc_pre.cc",
]
required_replacements = [
"src/emc/rs274ngc/canonmodule.cc -> core/wasm_shims/linuxcnc_runtime_shim.cc",
"src/emc/rs274ngc/gcodemodule.cc -> core/wasm_shims/linuxcnc_runtime_shim.cc",
"src/emc/rs274ngc/interpmodule.cc -> core/wasm_shims/linuxcnc_runtime_shim.cc",
"src/emc/rs274ngc/pyarrays.cc -> core/wasm_shims/python_c_api_shim.cc",
"src/emc/rs274ngc/pyblock.cc -> core/wasm_shims/python_c_api_shim.cc",
"src/emc/rs274ngc/pyemctypes.cc -> core/wasm_shims/python_c_api_shim.cc",
"src/emc/rs274ngc/pyinterp1.cc -> core/wasm_shims/python_c_api_shim.cc",
"src/emc/rs274ngc/pyparamclass.cc -> core/wasm_shims/python_c_api_shim.cc",
"src/emc/tooldata/tooldata_mmap.cc -> core/wasm_shims/tooldata/tooldata_mmap_backend.cc",
]
for entry in required_tracked_entries:
if " -> " in entry:
source, shim = entry.split(" -> ", 1)
if source not in sections.get("core-dlopen-shimmed", []):
raise SystemExit(f"WASM blocker analyzer output missing core dlopen shimmed source: {source}")
if shim not in shim_paths:
raise SystemExit(f"WASM blocker analyzer core dlopen shim is absent from wasm-safe shim set: {shim}")
elif entry not in output:
raise SystemExit(f"WASM blocker analyzer output missing required entry: {entry}")
if entry not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-blockers-source-map.md missing required entry: {entry}")
replacement_entries = [entry for entry in sections.get("blocked-replacements", []) if entry != "(none)"]
if replacement_entries != required_replacements:
raise SystemExit(f"WASM blocker replacement set changed: {replacement_entries}")
if "Required replacement set:" not in source_map:
raise SystemExit("docs/linuxcnc-wasm-blockers-source-map.md must document the complete replacement set")
for entry in required_replacements:
if entry not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-blockers-source-map.md missing required replacement: {entry}")
for replacement in re.findall(r"-> (core/wasm_shims/[^\s]+)", output):
if replacement not in shim_paths:
raise SystemExit(f"blocked replacement is absent from wasm-safe shim set: {replacement}")
for phrase in [
"does not add CNC behavior",
"must not become smoke parser behavior",
"blocked `dlopen` and blocked tooldata-users remain empty",
'parameter-file replacement through `filename + ".new"`',
"O-word lookup and parameter-file replacement",
"Python/Boost.Python remap paths remain tracked blockers",
"./check-linuxcnc-wasm-blockers-source-map.sh",
"does not interpret G-code",
]:
if phrase not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-blockers-source-map.md missing boundary phrase: {phrase}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-wasm-blockers-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-wasm-blockers-source-map.sh")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "CNC_SIM_WASM_BLOCKERS_SELF_CHECKS=1" not in text:
raise SystemExit(f"{entry} must run wasm blocker analyzer self-checks")
PY

View File

@@ -0,0 +1,499 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties the documented wasm-safe shim map to
# the linuxcnc_wasm_safe_probe_shims CMake set, the RS274/WASM source manifest,
# and the LinuxCNC sources each shim cites. It validates source coverage only;
# it does not add CNC behavior, expand smoke parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest"
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F 'LinuxCNC source basis: wasm-safe shim probe sources are limited to the' \
list-linuxcnc-wasm-safe-shims.sh >/dev/null
grep -F 'set(linuxcnc_wasm_safe_probe_shims' core/CMakeLists.txt >/dev/null
grep -F 'LinuxCNC source basis: this guard ties the documented WASM blocker map' \
check-linuxcnc-wasm-blockers-source-map.sh >/dev/null
grep -F 'dlopen(NULL, RTLD_GLOBAL);' "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F 'interp_lib = dlopen(interp_path, RTLD_NOW);' "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F 'Constructor constructor = (Constructor)dlsym(interp_lib, "makeInterp");' \
"$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F 'dlerror()' "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F '#include "libintl.h"' "$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F '#define _(s) gettext(s)' "$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F 'CHKS((!_readers[(int)' "$linuxcnc_root/src/emc/rs274ngc/interp_internal.cc" >/dev/null
grep -F 'Cannot use polar coordinate on a machine lacking X or Y axes' \
"$linuxcnc_root/src/emc/rs274ngc/interp_internal.cc" >/dev/null
grep -F 'int _task = 0;' "$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'int _task = 1;' "$linuxcnc_root/src/emc/task/emctaskmain.cc" >/dev/null
grep -F '{ "interpreter", PyInit_interpreter },' "$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F '{ "emccanon", PyInit_emccanon },' "$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'struct _inittab builtin_modules[]' "$linuxcnc_root/src/emc/task/taskclass.cc" >/dev/null
grep -F 'int Interp::fetch_hal_param(' "$linuxcnc_root/src/emc/rs274ngc/interp_namedparams.cc" >/dev/null
grep -F 'hal_get_pin_value_by_name(hal_name, &type, &ptr, &conn)' \
"$linuxcnc_root/src/emc/rs274ngc/interp_namedparams.cc" >/dev/null
grep -F 'hal_get_signal_value_by_name(hal_name, &type, &ptr, &conn)' \
"$linuxcnc_root/src/emc/rs274ngc/interp_namedparams.cc" >/dev/null
grep -F 'hal_get_param_value_by_name(hal_name, &type, &ptr)' \
"$linuxcnc_root/src/emc/rs274ngc/interp_namedparams.cc" >/dev/null
grep -F 'HAL_TYPE_UNINITIALIZED = 0,' "$linuxcnc_root/include/hal.h" >/dev/null
grep -F 'extern int hal_get_pin_value_by_name(' "$linuxcnc_root/include/hal.h" >/dev/null
grep -F 'extern int hal_get_signal_value_by_name(' "$linuxcnc_root/include/hal.h" >/dev/null
grep -F 'extern int hal_get_param_value_by_name(' "$linuxcnc_root/include/hal.h" >/dev/null
grep -F 'int hal_get_pin_value_by_name(' "$linuxcnc_root/src/hal/hal_lib.c" >/dev/null
grep -F 'int hal_get_signal_value_by_name(' "$linuxcnc_root/src/hal/hal_lib.c" >/dev/null
grep -F 'int hal_get_param_value_by_name(' "$linuxcnc_root/src/hal/hal_lib.c" >/dev/null
grep -F 'wordexp(basename, &exp_result, 0);' "$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'wordexp(settings->program_prefix, &exp_result, 0);' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'wordexp(settings->subroutines[dct], &exp_result, 0);' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'wordfree(&exp_result);' "$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F '#include "nml_intf/emc_nml.hh"' "$linuxcnc_root/src/emc/sai/dummyemcstat.cc" >/dev/null
grep -F 'EMC_STAT *emcStatus = new EMC_STAT;' "$linuxcnc_root/src/emc/sai/dummyemcstat.cc" >/dev/null
grep -F 'int NMLmsg::automatically_clear = 1;' \
"$linuxcnc_root/src/libnml/nml/nmlmsg.cc" >/dev/null
grep -F 'NMLmsg::NMLmsg' "$linuxcnc_root/src/libnml/nml/nmlmsg.cc" >/dev/null
grep -F 'void NMLmsg::clear()' "$linuxcnc_root/src/libnml/nml/nmlmsg.cc" >/dev/null
grep -F 'memset((void *) this, 0, size);' \
"$linuxcnc_root/src/libnml/nml/nmlmsg.cc" >/dev/null
grep -F 'RCS_STAT_MSG::RCS_STAT_MSG' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F 'command_type = -1;' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F 'echo_serial_number = -1;' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F 'status = RCS_STATUS::UNINITIALIZED;' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F '_state = -1;' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F 'extern int set_print_rcs_error_info(const char *file, int line);' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.hh" >/dev/null
grep -F 'extern int print_rcs_error_new(const char *_fmt, ...)' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.hh" >/dev/null
grep -F '#define rcs_print_error set_print_rcs_error_info( __FILE__, __LINE__); print_rcs_error_new' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.hh" >/dev/null
grep -F 'int set_print_rcs_error_info(const char *file, int line)' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.cc" >/dev/null
grep -F 'print_rcs_error_new' "$linuxcnc_root/src/libnml/rcs/rcs_print.cc" >/dev/null
grep -F 'retval = rcs_vprint(_fmt, args, 1);' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.cc" >/dev/null
grep -F 'extern int rtapi_snprintf(char *buf, unsigned long int size,' \
"$linuxcnc_root/src/rtapi/rtapi.h" >/dev/null
grep -F 'extern int rtapi_vsnprintf(char *buf, unsigned long size,' \
"$linuxcnc_root/src/rtapi/rtapi.h" >/dev/null
grep -F 'int rtapi_snprintf(char *buffer, unsigned long int size, const char *msg, ...) {' \
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
grep -F 'result = vsnprintf(buffer, size, msg, args);' \
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
grep -F 'int rtapi_vsnprintf(char *buffer, unsigned long int size, const char *fmt,' \
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
grep -F 'return vsnprintf(buffer, size, fmt, args);' \
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
grep -F 'class PythonPlugin' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
grep -F 'PLUGIN_PYTHON_NOT_INITIALIZED = -12,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
grep -F 'PLUGIN_BAD_PATH = -5,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
grep -F 'PLUGIN_INITTAB_FAILED = -7 ,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
grep -F 'PLUGIN_EXCEPTION_DURING_PATH_PREPEND = -9,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
grep -F 'PLUGIN_EXCEPTION_DURING_PATH_APPEND = -10,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
grep -F 'PLUGIN_NO_CALLABLE = 1,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
grep -F 'PLUGIN_EXCEPTION = 2' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
grep -F 'PythonPlugin::instantiate' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'for (int i = 0; inittab[i].name != NULL; i++) {' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'if (module == NULL) {' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'status = PLUGIN_INITTAB_FAILED;' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'if (status < PLUGIN_OK)' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'if (auto inistring = inifile.findString("TOPLEVEL", section)) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'if (realpath(toplevel, real_path) == NULL) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'status = PLUGIN_BAD_PATH;' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'while (auto inistring = inifile.findString(n, "PATH_PREPEND", "PYTHON")) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'while (auto inistring = inifile.findString(n, "PATH_APPEND", "PYTHON")) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'status = PLUGIN_EXCEPTION_DURING_PATH_PREPEND;' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'status = PLUGIN_EXCEPTION_DURING_PATH_APPEND;' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'int IniFile::tildeExpand(const std::string &path, std::string &result)' \
"$linuxcnc_root/src/emc/ini/inifile.cc" >/dev/null
grep -F "if(!home)" "$linuxcnc_root/src/emc/ini/inifile.cc" >/dev/null
grep -F 'IniFile::trim(sect);' "$linuxcnc_root/src/emc/ini/inifile.cc" >/dev/null
grep -F 'Section header has trailing content, ignored' "$linuxcnc_root/src/emc/ini/inifile.cc" >/dev/null
grep -F 'retval = bp::exec_file(cmd, main_namespace, main_namespace);' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'retval = bp::exec(cmd, main_namespace, main_namespace);' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'PyObject *rv = PyObject_Call(function.ptr(), tupleargs.ptr(), kwargs.ptr());' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'PyErr_Fetch(&exc,&val,&tb);' \
"$linuxcnc_root/src/emc/rs274ngc/interp_python.cc" >/dev/null
grep -F 'PyErr_NormalizeException(&exc,&val,&tb);' \
"$linuxcnc_root/src/emc/rs274ngc/interp_python.cc" >/dev/null
grep -F 'if (Py_IsInitialized()) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F '#define callmethod(o, m, f, ...) PyObject_CallMethod((o), (char*)(m), (char*)(f), ## __VA_ARGS__)' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyObject *result = PyObject_GetAttrString(callback, "parameter_file");' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyErr_Format(PyExc_TypeError,' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'return Py_BuildValue("[ddd][ddd][ddd][ddd]",' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyObject *m = PyModule_Create(&gcode_moduledef);' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyType_Ready(&LineCodeType);' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyModule_AddObject(m, "linecode", (PyObject*)&LineCodeType);' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyObject_SetAttrString(m, "MAX_ERROR", PyLong_FromLong(maxerror));' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F '#define TOOL_MMAP_FILENAME ".tool.mmap"' "$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'int tool_nml_register(CANON_TOOL_TABLE *tblptr)' "$linuxcnc_root/src/emc/tooldata/tooldata_nml.cc" >/dev/null
grep -F 'int tooldata_db_init(char progname_plus_args[]' "$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'if (!db_live) {return -1;}' "$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'if (!db_live) return 0;' "$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
python3 - "$manifest" <<'PY'
from pathlib import Path
import re
import subprocess
import sys
manifest = Path(sys.argv[1])
source_map = Path("docs/linuxcnc-wasm-shims-source-map.md").read_text(encoding="utf-8")
manifest_text = manifest.read_text(encoding="utf-8")
shim_paths = [
line for line in subprocess.check_output(["./list-linuxcnc-wasm-safe-shims.sh"], text=True).splitlines()
if line
]
if len(shim_paths) != 11:
raise SystemExit(f"expected 11 wasm-safe CMake shim sources, found {len(shim_paths)}")
if f"| CMake `linuxcnc_wasm_safe_probe_shims` sources | {len(shim_paths)} |" not in source_map:
raise SystemExit("docs/linuxcnc-wasm-shims-source-map.md shim count mismatch")
expected_shims = {
"core/wasm_shims/dlfcn.cc": ["src/emc/rs274ngc/interp_base.cc"],
"core/wasm_shims/emc_status_shim.cc": ["src/emc/sai/dummyemcstat.cc"],
"core/wasm_shims/gettext_shim.cc": ["src/emc/rs274ngc/interp_internal.hh"],
"core/wasm_shims/linuxcnc_runtime_shim.cc": [
"src/emc/rs274ngc/gcodemodule.cc",
"src/emc/task/emctaskmain.cc",
"src/emc/task/taskclass.cc",
"src/emc/rs274ngc/interp_namedparams.cc",
"include/hal.h",
"src/hal/hal_lib.c",
"src/emc/rs274ngc/rs274ngc_pre.cc",
],
"core/wasm_shims/nml_status_shim.cc": [
"src/libnml/nml/nmlmsg.cc",
"src/libnml/nml/stat_msg.cc",
],
"core/wasm_shims/python_c_api_shim.cc": [
"src/emc/rs274ngc/interp_python.cc",
"src/emc/rs274ngc/interp_namedparams.cc",
"src/emc/rs274ngc/gcodemodule.cc",
"src/emc/pythonplugin/python_plugin.cc",
],
"core/wasm_shims/rcs_print_shim.cc": [
"src/libnml/rcs/rcs_print.cc",
"src/libnml/rcs/rcs_print.hh",
],
"core/wasm_shims/rtapi_compat.cc": [
"src/rtapi/uspace_common.h",
"src/rtapi/rtapi.h",
],
"core/wasm_shims/tooldata/tooldata_mmap_backend.cc": [
"src/emc/tooldata/tooldata_mmap.cc",
],
"core/wasm_shims/tooldata/tooldata_runtime_stubs.cc": [
"src/emc/tooldata/tooldata_nml.cc",
"src/emc/tooldata/tooldata_db.cc",
],
"core/wasm_shims/pythonplugin/python_plugin.cc": [
"src/emc/pythonplugin/python_plugin.cc",
"src/emc/pythonplugin/python_plugin.hh",
"src/emc/ini/inifile.cc",
],
}
if shim_paths != list(expected_shims):
raise SystemExit("wasm-safe shim CMake order no longer matches docs/linuxcnc-wasm-shims-source-map.md")
for shim_path, linuxcnc_sources in expected_shims.items():
shim_text = Path(shim_path).read_text(encoding="utf-8")
if "LinuxCNC source basis:" not in shim_text:
raise SystemExit(f"{shim_path} is missing a LinuxCNC source basis comment")
if f"| `{shim_path}` |" not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-shims-source-map.md missing shim row: {shim_path}")
for source in linuxcnc_sources:
if source not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-shims-source-map.md missing source {source} for {shim_path}")
if source not in manifest_text:
raise SystemExit(f"{source} is not tracked in {manifest}")
runtime_shim = Path("core/wasm_shims/linuxcnc_runtime_shim.cc").read_text(encoding="utf-8")
runtime_probe = Path("core/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc").read_text(encoding="utf-8")
dlfcn_shim = Path("core/wasm_shims/dlfcn.cc").read_text(encoding="utf-8")
dlfcn_header = Path("core/wasm_shims/dlfcn.h").read_text(encoding="utf-8")
emc_status_shim = Path("core/wasm_shims/emc_status_shim.cc").read_text(encoding="utf-8")
gettext_shim = Path("core/wasm_shims/gettext_shim.cc").read_text(encoding="utf-8")
for needle in [
"#define RTLD_GLOBAL 0x00100",
"#define RTLD_NOW 0x00002",
"void *dlopen(const char *filename, int flags);",
"void *dlsym(void *handle, const char *symbol);",
"char *dlerror(void);",
"int dlclose(void *handle);",
]:
if needle not in dlfcn_header:
raise SystemExit(f"dlfcn shim header missing LinuxCNC loader boundary: {needle}")
for needle in [
"dynamic loading is unavailable in wasm-safe probe",
"void *dlopen(const char *, int)",
"return nullptr;",
"void *dlsym(void *, const char *)",
"char *dlerror(void)",
"return last_error;",
"int dlclose(void *)",
"return 0;",
]:
if needle not in dlfcn_shim:
raise SystemExit(f"dlfcn shim missing unavailable-loader boundary: {needle}")
for needle in [
'#include "nml_intf/emc_nml.hh"',
"EMC_STAT *emcStatus = new EMC_STAT;",
]:
if needle not in emc_status_shim:
raise SystemExit(f"EMC status shim missing detached LinuxCNC status singleton: {needle}")
for needle in [
"extern \"C\" char *gettext(const char *msgid)",
"return const_cast<char *>(msgid ? msgid : \"\");",
]:
if needle not in gettext_shim:
raise SystemExit(f"gettext shim missing diagnostic passthrough boundary: {needle}")
nml_status_shim = Path("core/wasm_shims/nml_status_shim.cc").read_text(encoding="utf-8")
for needle in [
"int NMLmsg::automatically_clear = 1;",
"void NMLmsg::clear()",
"std::memset(static_cast<void *>(this), 0, static_cast<size_t>(size));",
"command_type = -1;",
"echo_serial_number = -1;",
"status = RCS_STATUS::UNINITIALIZED;",
"_state = -1;",
]:
if needle not in nml_status_shim:
raise SystemExit(f"NML status shim missing LinuxCNC constructor boundary: {needle}")
for needle in [
"int _task = 0;",
'extern "C" PyObject *PyInit_interpreter(void)',
'extern "C" PyObject *PyInit_emccanon(void)',
'extern "C" PyObject *PyInit_gcode(void)',
"return nullptr;",
'struct _inittab builtin_modules[] = {',
'{"interpreter", PyInit_interpreter}',
'{"emccanon", PyInit_emccanon}',
]:
if needle not in runtime_shim:
raise SystemExit(f"linuxcnc runtime shim missing preview/builtin module boundary: {needle}")
for needle in [
"std::string expand_home_directory(const char *words)",
"std::string expand_environment_variables(const std::string &words)",
"extern \"C\" int wordexp(const char *words, wordexp_t *pwordexp, int)",
"return WRDE_BADVAL;",
"return WRDE_NOSPACE;",
"pwordexp->we_wordc = 1;",
"pwordexp->we_offs = 0;",
"extern \"C\" void wordfree(wordexp_t *pwordexp)",
"pwordexp->we_wordc = 0;",
"pwordexp->we_wordv = nullptr;",
]:
if needle not in runtime_shim:
raise SystemExit(f"linuxcnc runtime shim missing limited wordexp boundary: {needle}")
for needle in [
'extern "C" int hal_get_pin_value_by_name',
'extern "C" int hal_get_signal_value_by_name',
'extern "C" int hal_get_param_value_by_name',
"*type = HAL_TYPE_UNINITIALIZED;",
"*data = nullptr;",
"return -1;",
]:
if needle not in runtime_shim:
raise SystemExit(f"linuxcnc runtime shim missing HAL unavailable boundary: {needle}")
for needle in [
"bool hal_lookup_is_unavailable()",
'hal_get_pin_value_by_name("cnc-sim.missing-pin", &type, &data, &connected) != -1',
'hal_get_signal_value_by_name("cnc-sim.missing-signal", &type, &data, &has_writers) != -1',
'hal_get_param_value_by_name("cnc-sim.missing-param", &type, &data) != -1',
"type != HAL_TYPE_UNINITIALIZED",
"data != nullptr",
]:
if needle not in runtime_probe:
raise SystemExit(f"runtime probe missing HAL unavailable assertion: {needle}")
python_plugin_shim = Path("core/wasm_shims/pythonplugin/python_plugin.cc").read_text(encoding="utf-8")
python_plugin_probe = Path("core/wasm_shims/pythonplugin/python_plugin_probe_main.cc").read_text(encoding="utf-8")
python_plugin_inittab_probe = Path("core/wasm_shims/pythonplugin/python_plugin_inittab_probe_main.cc").read_text(encoding="utf-8")
python_c_api_shim = Path("core/wasm_shims/python_c_api_shim.cc").read_text(encoding="utf-8")
for forbidden in [
"bp::exec(",
"bp::exec_file(",
"PyObject_Call(",
]:
if forbidden in python_plugin_shim:
raise SystemExit(f"PythonPlugin wasm shim must not execute Python: {forbidden}")
for needle in [
"python disabled in wasm-safe probe",
'find_ini_value(iniFilename, section, "TOPLEVEL", toplevel)',
"realpath(toplevel.c_str(), resolved_path) == nullptr",
"status = PLUGIN_BAD_PATH;",
"status = PLUGIN_INITTAB_FAILED;",
"failed to initialize built-in module",
"read_ini_section(text, section_name)",
"section_name = trim_ini_value(text.substr(1, close - 1));",
'check_python_path_entries(iniFilename,',
"PLUGIN_EXCEPTION_DURING_PATH_PREPEND",
"PLUGIN_EXCEPTION_DURING_PATH_APPEND",
"tilde_expand_path(entry, expanded)",
"status = PLUGIN_PYTHON_NOT_INITIALIZED;",
"return PLUGIN_NO_CALLABLE;",
"status = PLUGIN_EXCEPTION;",
"if (status < PLUGIN_OK)",
]:
if needle not in python_plugin_shim:
raise SystemExit(f"PythonPlugin wasm shim missing disabled-python boundary: {needle}")
for needle in [
"PythonPlugin::PythonPlugin(struct _inittab*) reports PLUGIN_INITTAB_FAILED",
"PythonPlugin *plugin = PythonPlugin::instantiate(bad_modules);",
"plugin != nullptr",
"python_plugin->plugin_status() != PLUGIN_INITTAB_FAILED",
"python_plugin->usable()",
'python_plugin->last_errmsg() != "failed to initialize built-in module"',
]:
if needle not in python_plugin_inittab_probe:
raise SystemExit(f"PythonPlugin inittab probe missing first-caller status assertion: {needle}")
for needle in [
"[ PYTHON ] ; section comment",
"PLUGIN_NO_SECTION",
"PLUGIN_BAD_INIFILE",
"PLUGIN_BAD_PATH",
"PLUGIN_EXCEPTION_DURING_PATH_PREPEND",
"PLUGIN_EXCEPTION_DURING_PATH_APPEND",
"PLUGIN_PYTHON_NOT_INITIALIZED",
"PLUGIN_NO_CALLABLE",
"PLUGIN_EXCEPTION",
"handle_pyerror().empty()",
]:
if needle not in python_plugin_probe:
raise SystemExit(f"PythonPlugin probe missing disabled-python status assertion: {needle}")
for needle in [
"static PyTypeObject none_type = {\"NoneType\"};",
"static PyObject none_object = {&none_type};",
"PyObject *_Py_NoneStructPtr = &none_object;",
"int Py_IsInitialized(void)",
"return 1;",
"PyObject *PyErr_Occurred(void)",
"return nullptr;",
"void PyErr_Fetch(PyObject **exc, PyObject **val, PyObject **tb)",
"*exc = nullptr;",
"*val = nullptr;",
"*tb = nullptr;",
"int PyCallable_Check(PyObject *)",
"return 0;",
"PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...)",
"PyObject *PyObject_GetAttrString(PyObject *, const char *)",
"return &none_object;",
"PyObject *Py_BuildValue(const char *, ...)",
"PyObject *PyModule_Create(PyModuleDef *)",
"int PyModule_AddObject(PyObject *, const char *, PyObject *)",
"int PyType_Ready(PyTypeObject *)",
"void *PyObject_NewShim(std::size_t size, PyTypeObject *type)",
"std::calloc(1, size)",
]:
if needle not in python_c_api_shim:
raise SystemExit(f"Python C API shim missing symbol boundary: {needle}")
rcs_print_shim = Path("core/wasm_shims/rcs_print_shim.cc").read_text(encoding="utf-8")
for needle in [
"set_print_rcs_error_info",
"print_rcs_error_new",
"std::vfprintf(stderr, format, args)",
]:
if needle not in rcs_print_shim:
raise SystemExit(f"RCS print shim missing split print-error boundary: {needle}")
rtapi_compat_shim = Path("core/wasm_shims/rtapi_compat.cc").read_text(encoding="utf-8")
for needle in [
"rtapi_snprintf",
"std::vsnprintf(buf, static_cast<std::size_t>(size), fmt, ap)",
]:
if needle not in rtapi_compat_shim:
raise SystemExit(f"RTAPI compatibility shim missing userspace snprintf boundary: {needle}")
for phrase in [
"must not add CNC behavior",
"must not expand the temporary smoke parser",
"CMake `linuxcnc_wasm_safe_probe_shims` set",
"RS274/WASM manifest",
"browser-safe LinuxCNC adaptations",
"LinuxCNC `interp_base.cc` dynamic loader calls",
"unavailable-loader returns",
"detached LinuxCNC `EMC_STAT` singleton",
"LinuxCNC `_()` diagnostic call sites",
"browser gettext catalogs unavailable",
"LinuxCNC NML status constructors",
"`RCS_STAT_MSG` default status fields",
"dynamic loading unavailable",
"unavailable HAL lookup",
"must leave HAL values unavailable",
"preview `_task=0`",
"builtin module stubs",
"must not switch browser-safe probes into task mode",
"single-word `wordexp()` path expansion",
"`wordfree()` cleanup",
"must not become a shell expansion feature",
"must not become Python execution",
"Python C API shim must return inert objects",
"must not call into CPython runtime",
"keeping Python execution disabled",
"must not execute `bp::exec`, `bp::exec_file`, or",
"inactive-DB return values",
"negative-status short-circuit behavior",
"first-caller `_inittab` `PLUGIN_INITTAB_FAILED` edge",
"preserve the `TOPLEVEL` missing-path `PLUGIN_BAD_PATH` edge",
"preserve `PATH_PREPEND` and `PATH_APPEND` tilde-expansion failure statuses",
"preserve LinuxCNC INI section-name trimming and trailing section-comment handling",
"LinuxCNC `rcs_print_error` split entry points",
"without native print routing",
"LinuxCNC userspace `rtapi_snprintf()` formatting",
"`uspace_common.h` `vsnprintf()` implementation path",
"./check-linuxcnc-wasm-shims-source-map.sh",
"does not interpret G-code",
]:
if phrase not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-shims-source-map.md missing boundary phrase: {phrase}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-wasm-shims-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-wasm-shims-source-map.sh")
cmake_text = Path("core/CMakeLists.txt").read_text(encoding="utf-8")
for shim_path in shim_paths:
cmake_suffix = shim_path.removeprefix("core/")
if cmake_suffix not in cmake_text:
raise SystemExit(f"{shim_path} is not listed in linuxcnc_wasm_safe_probe_shims")
blocker_map = Path("docs/linuxcnc-wasm-blockers-source-map.md").read_text(encoding="utf-8")
for shim_path in [
"core/wasm_shims/linuxcnc_runtime_shim.cc",
"core/wasm_shims/python_c_api_shim.cc",
"core/wasm_shims/tooldata/tooldata_mmap_backend.cc",
]:
if shim_path not in blocker_map:
raise SystemExit(f"docs/linuxcnc-wasm-blockers-source-map.md no longer references {shim_path}")
PY

View File

@@ -0,0 +1,204 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties Node/browser WASM smoke coverage to
# LinuxCNC RS274 parameter/INI handling, M-code canon side effects, generated
# switchkins/remap config cases, kinematics sources, and OPFS-only browser
# persistence. It validates smoke-test source coverage only; it does not add
# CNC behavior, expand smoke parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F 'int Interp::ini_load(const char *filename)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'int Interp::restore_parameters(const char *filename)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'int Interp::save_parameters(const char *filename,' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'std::string tempfile = std::string(filename) + ".new";' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'unlink(bakfile.c_str());' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'if(link(filename, bakfile.c_str()) < 0)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'if(rename(tempfile.c_str(), filename) < 0)' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'read_text(command, _setup.file_pointer, _setup.linetext,' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'int read_text(const char *command, FILE * inport, char *raw_line,' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_interp.hh" >/dev/null
grep -F 'int Interp::convert_m(block_pointer block' \
"$linuxcnc_root/src/emc/rs274ngc/interp_convert.cc" >/dev/null
grep -F 'int ret = WAIT(round_to_int(block->p_number), DIGITAL_INPUT, type, timeout);' \
"$linuxcnc_root/src/emc/rs274ngc/interp_convert.cc" >/dev/null
grep -F 'SET_AUX_OUTPUT_VALUE(round_to_int(block->e_number), block->q_number);' \
"$linuxcnc_root/src/emc/rs274ngc/interp_convert.cc" >/dev/null
grep -F 'extern int WAIT(int index' "$linuxcnc_root/src/emc/nml_intf/canon.hh" >/dev/null
grep -F 'extern void SET_AUX_OUTPUT_VALUE(int index, double value);' \
"$linuxcnc_root/src/emc/nml_intf/canon.hh" >/dev/null
grep -F 'kp->kinsname = "5axiskins";' "$linuxcnc_root/src/emc/kinematics/5axiskins.c" >/dev/null
grep -F 'int xyzbcKinematicsInverse(const EmcPose * pos' \
"$linuxcnc_root/src/emc/kinematics/trtfuncs.c" >/dev/null
grep -F 'int xyzacKinematicsInverse(const EmcPose * pos' \
"$linuxcnc_root/src/emc/kinematics/trtfuncs.c" >/dev/null
grep -F '*kinv1 = xyzacKinematicsInverse;' \
"$linuxcnc_root/src/emc/kinematics/xyzac-trt-kins.c" >/dev/null
grep -F '*kinv1 = xyzbcKinematicsInverse;' \
"$linuxcnc_root/src/emc/kinematics/xyzbc-trt-kins.c" >/dev/null
grep -F 'int userkKinematicsInverse(const EmcPose * pos' \
"$linuxcnc_root/src/emc/kinematics/userkfuncs.c" >/dev/null
grep -F 'LinuxCNC WASM smoke source map' docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F 'must not expand the temporary smoke parser' docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F 'OPFS-only' docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F '`src/emc/rs274ngc/interp_convert.cc` provides `convert_m()` coverage' \
docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F 'unlinking `filename + ".bak"`' docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F '`src/emc/kinematics/5axiskins.c`, `src/emc/kinematics/trtfuncs.c`' \
docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F 'Generated switchkins config smoke cases must continue to come from' \
docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F 'LinuxCNC source basis: Node smoke only verifies the wasm/API bridge around' \
test-web-wasm-node-smoke.sh >/dev/null
grep -F 'minimum_steps=${WASM_NODE_SMOKE_MIN_STEPS:-50}' test-web-wasm-node-smoke.sh >/dev/null
grep -F 'WASM Node smoke covered $passed_steps steps, expected at least $minimum_steps' \
test-web-wasm-node-smoke.sh >/dev/null
grep -F 'expected Node wasm-core wrapper to leave OPFS disabled' test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'expected Node wasm-core parseFile to require OPFS' test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'linuxcnc_switchkins_remap_config_cases.json' test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'expected LinuxCNC WASM M68 E3 Q${kinstype} remap side effect' \
test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'expected LinuxCNC WASM M66 E0 L0 remap side effect' \
test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'expected LinuxCNC WASM 5axiskins RTCP pivot from LinuxCNC 5axiskins inverse' \
test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'expected LinuxCNC WASM configured xyzbc-trt RTCP pivot from LinuxCNC trtfuncs inverse' \
test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'expected LinuxCNC WASM configured xyzac-trt RTCP pivot from LinuxCNC xyzab_tdr inverse' \
test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'LinuxCNC source basis: browser smoke verifies OPFS-backed wasm/API/app flows' \
test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'minimum_sections=${BROWSER_SMOKE_MIN_SECTIONS:-50}' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'BROWSER_SMOKE_MIN_SECTIONS must be a positive integer' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'browser WASM smoke covered $passed_sections sections, expected at least $minimum_sections' \
test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'required_browser_sections = {' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("linuxcnc generated switchkins cases load"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("opfs parameter restore in new instance"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("opfs directory persist and load"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("browser opfs policy"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runSection("browser app opfs restore"' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'runLinuxCncBrowserSections(context, simulator)' web/test-browser-wasm-smoke-sections.js >/dev/null
grep -F 'runBrowserOpfsWorkspaceSections(context, simulator)' web/test-browser-wasm-smoke-sections.js >/dev/null
grep -F 'runBrowserOpfsPolicySections(context)' web/test-browser-wasm-smoke-sections.js >/dev/null
grep -F 'runBrowserAppOpfsSections(context)' web/test-browser-wasm-smoke-sections.js >/dev/null
grep -F 'runBrowserOpfsBasicSections(context, simulator)' \
web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'runBrowserOpfsParameterSections(context, simulator)' \
web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'runBrowserOpfsMirrorSections(context, simulator)' \
web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'runBrowserOpfsDirectorySections(context, simulator)' \
web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'linuxcnc_switchkins_remap_config_cases.json' \
web/test-browser-wasm-smoke-linuxcnc-sections.js >/dev/null
grep -F 'missing LinuxCNC switchkins config cases after INI_FILE_NAME remap-load filtering' \
web/test-browser-wasm-smoke-linuxcnc-sections.js >/dev/null
grep -F 'LinuxCNC source basis: rs274ngc_pre.cc restore_parameters()' \
web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC source basis: rs274ngc_pre.cc save_parameters()' \
web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC source basis: rs274ngc_pre.cc read_text() reads' \
web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'runSection("opfs directory persist and load"' \
web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'runSection("opfs metadata queries avoid wasm mirror pollution"' \
web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'runSection("browser opfs policy"' \
web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
grep -F 'app frame did not persist current program into OPFS' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not persist LinuxCNC parameter backup into OPFS' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not clean up LinuxCNC temporary parameter file in OPFS' \
web/test-browser-wasm-smoke-app-sections.js >/dev/null
if grep -R -n -E 'localStorage|sessionStorage|indexedDB|showOpenFilePicker|showSaveFilePicker|showDirectoryPicker|webkitRequestFileSystem|FileReader' web/src; then
echo "browser app/wasm code must not add filesystem persistence outside OPFS" >&2
exit 1
fi
if grep -n -E 'localStorage|sessionStorage|indexedDB|showOpenFilePicker|showSaveFilePicker|showDirectoryPicker|webkitRequestFileSystem|FileReader' \
web/test-browser-wasm-smoke-*.js \
web/test-browser-wasm-smoke.html \
test-web-wasm-browser-smoke.sh; then
echo "browser WASM smoke harness must not add filesystem persistence outside OPFS" >&2
exit 1
fi
python3 - <<'PY'
from pathlib import Path
import json
import re
source_map = Path("docs/linuxcnc-wasm-smoke-source-map.md").read_text(encoding="utf-8")
json_cases = json.loads(Path("web/public/linuxcnc_switchkins_remap_config_cases.json").read_text(encoding="utf-8"))
if not isinstance(json_cases, list) or len(json_cases) == 0:
raise SystemExit("generated LinuxCNC switchkins browser config cases are missing")
node_wrapper = Path("test-web-wasm-node-smoke.sh").read_text(encoding="utf-8")
browser_wrapper = Path("test-web-wasm-browser-smoke.sh").read_text(encoding="utf-8")
node_min = re.search(r"minimum_steps=\$\{WASM_NODE_SMOKE_MIN_STEPS:-(\d+)\}", node_wrapper)
browser_min = re.search(r"minimum_sections=\$\{BROWSER_SMOKE_MIN_SECTIONS:-(\d+)\}", browser_wrapper)
if not node_min or int(node_min.group(1)) < 50:
raise SystemExit("Node WASM smoke minimum step threshold is missing or below 50")
if not browser_min or int(browser_min.group(1)) < 50:
raise SystemExit("browser WASM smoke minimum section threshold is missing or below 50")
for path in [
"test-web-wasm-node-smoke.sh",
"test-web-wasm-node-smoke.cjs",
"test-web-wasm-browser-smoke.sh",
"web/test-browser-wasm-smoke-linuxcnc-sections.js",
"web/test-browser-wasm-smoke-opfs-workspace-sections.js",
"web/test-browser-wasm-smoke-opfs-basic-sections.js",
"web/test-browser-wasm-smoke-opfs-parameter-sections.js",
"web/test-browser-wasm-smoke-opfs-mirror-sections.js",
"web/test-browser-wasm-smoke-opfs-directory-sections.js",
"web/test-browser-wasm-smoke-opfs-policy-sections.js",
"web/test-browser-wasm-smoke-app-sections.js",
]:
if f"| `{path}` |" not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-smoke-source-map.md missing smoke entry: {path}")
required_phrases = [
"must not add CNC behavior",
"must not expand the temporary smoke parser",
"persistence OPFS-only",
"Node smoke must leave OPFS disabled",
"Browser smoke harness files must not use Local Storage",
"Generated switchkins config smoke cases",
"browser smoke shell guard also pins required section names",
"OPFS workspace/policy/directory handling",
'unlinking `filename + ".bak"`',
"./check-linuxcnc-wasm-smoke-source-map.sh",
"does not interpret G-code",
]
for phrase in required_phrases:
if phrase not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-smoke-source-map.md missing boundary phrase: {phrase}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-wasm-smoke-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-wasm-smoke-source-map.sh")
PY

View File

@@ -0,0 +1,132 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties the six active workstream contexts to
# their source-map guards and to LinuxCNC source/config anchors. It validates
# coordination policy only; it does not add CNC behavior or expand smoke
# parsing.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F '1. `switchkins/remap` generator and tables' docs/architecture.md >/dev/null
grep -F '2. `linuxcnc-kinematics` source manifest' docs/architecture.md >/dev/null
grep -F '3. WASM filesystem and OPFS persistence' docs/architecture.md >/dev/null
grep -F '4. browser app integration' docs/architecture.md >/dev/null
grep -F '5. Node and browser smoke coverage' docs/architecture.md >/dev/null
grep -F '6. source-link, build, and documentation constraints' docs/architecture.md >/dev/null
grep -F 'The synchronized source-map guard for these contexts is' docs/architecture.md >/dev/null
grep -F '`docs/linuxcnc-workstreams-source-map.md`.' docs/architecture.md >/dev/null
grep -F 'REMAP = M428' "$linuxcnc_root/configs/sim/axis/vismach/5axis/bridgemill/5axis.ini" >/dev/null
grep -F '#<kinstype>' "$linuxcnc_root/configs/sim/axis/vismach/5axis/bridgemill/remap_subs/428remap.ngc" >/dev/null
grep -F 'GENSERKINSSRCS := emc/kinematics/ugenserkins.c' \
"$linuxcnc_root/src/emc/kinematics/Submakefile" >/dev/null
grep -F 'extern int kinematicsInverse(const struct EmcPose * world,' \
"$linuxcnc_root/src/emc/kinematics/kinematics.h" >/dev/null
grep -F '#define RS274NGC_PARAMETER_FILE_NAME_DEFAULT "rs274ngc.var"' \
"$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F 'int Interp::save_parameters(const char *filename,' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'LIBRS274SRCS := $(addprefix emc/rs274ngc/,' \
"$linuxcnc_root/src/emc/rs274ngc/Submakefile" >/dev/null
grep -F 'LIBTOOLDATA_SRCS += emc/tooldata/tooldata_common.cc' \
"$linuxcnc_root/src/emc/tooldata/Submakefile" >/dev/null
grep -F 'LIBLCNCINISRCS := emc/ini/inifile.cc' \
"$linuxcnc_root/src/emc/ini/Submakefile" >/dev/null
grep -F 'LIBPPSRCS := $(addprefix emc/pythonplugin/,' \
"$linuxcnc_root/src/emc/pythonplugin/Submakefile" >/dev/null
grep -F 'LinuxCNC workstreams source map' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'must not add CNC' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'temporary smoke parser' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'Every active workstream must have at least one source-map document and guard' \
docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'docs/linuxcnc-source-policy.md` must keep the same one-context workflow' \
docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'Browser filesystem persistence remains OPFS-only.' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'Smoke coverage remains verification only' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'Keep the change inside one active workstream context unless a dependency is' \
docs/linuxcnc-source-policy.md >/dev/null
grep -F 'Each pass should advance one reasonable, reviewable context.' \
docs/linuxcnc-source-policy.md >/dev/null
grep -F 'Keep the six active workstream contexts synchronized with' \
docs/linuxcnc-source-policy.md >/dev/null
grep -F '`docs/linuxcnc-workstreams-source-map.md` so source-map guards' \
docs/linuxcnc-source-policy.md >/dev/null
grep -F 'Smoke tests are verification guardrails. They must not be expanded with new' \
docs/linuxcnc-source-policy.md >/dev/null
grep -F 'Do not mix generator/table work, manifests, OPFS/browser work, and build' \
docs/linuxcnc-source-policy.md >/dev/null
grep -F 'Browser-side wasm filesystem behavior must use OPFS-backed storage.' \
docs/linuxcnc-source-policy.md >/dev/null
python3 - <<'PY'
from pathlib import Path
doc = Path("docs/linuxcnc-workstreams-source-map.md").read_text(encoding="utf-8")
policy = Path("docs/linuxcnc-source-policy.md").read_text(encoding="utf-8")
expected = {
"switchkins/remap": [
"docs/linuxcnc-switchkins-remap-source-map.md",
"check-linuxcnc-switchkins-remap-source-map.sh",
],
"linuxcnc-kinematics": [
"docs/linuxcnc-kinematics-source-map.md",
"check-linuxcnc-kinematics-source-map.sh",
],
"WASM filesystem and OPFS persistence": [
"docs/linuxcnc-opfs-source-map.md",
"docs/linuxcnc-wasm-shims-source-map.md",
"docs/linuxcnc-wasm-blockers-source-map.md",
"docs/linuxcnc-tooldata-source-map.md",
"check-linuxcnc-opfs-source-map.sh",
"check-linuxcnc-wasm-shims-source-map.sh",
"check-linuxcnc-wasm-blockers-source-map.sh",
"check-linuxcnc-tooldata-source-map.sh",
],
"browser app integration": [
"docs/linuxcnc-browser-app-source-map.md",
"check-linuxcnc-browser-app-source-map.sh",
],
"Node and browser smoke coverage": [
"docs/linuxcnc-wasm-smoke-source-map.md",
"check-linuxcnc-wasm-smoke-source-map.sh",
],
"source-link, build, and documentation constraints": [
"docs/linuxcnc-rs274-source-map.md",
"docs/linuxcnc-build-source-map.md",
"docs/linuxcnc-tooldata-source-map.md",
"check-linuxcnc-rs274-source-map.sh",
"check-linuxcnc-build-source-map.sh",
"check-linuxcnc-tooldata-source-map.sh",
],
}
for workstream, paths in expected.items():
if workstream not in doc:
raise SystemExit(f"missing workstream row in docs/linuxcnc-workstreams-source-map.md: {workstream}")
policy_terms = {
"switchkins/remap": ["switchkins/remap", "M428/M429/M430"],
"linuxcnc-kinematics": ["kinematics manifest", "RTCP coverage"],
"WASM filesystem and OPFS persistence": ["OPFS", "filesystem"],
"browser app integration": ["Browser app", "wasm bridge"],
"Node and browser smoke coverage": ["Smoke tests are verification guardrails", "temporary smoke parser"],
"source-link, build, and documentation constraints": ["source-link", "build-policy"],
}[workstream]
if not any(term in policy for term in policy_terms):
raise SystemExit(f"docs/linuxcnc-source-policy.md no longer references workstream boundary: {workstream}")
for path in paths:
if path not in doc:
raise SystemExit(f"missing source-map path in workstream index: {path}")
if not Path(path).exists():
raise SystemExit(f"source-map path listed in workstream index does not exist: {path}")
if path.startswith("check-") and not Path(path).stat().st_mode & 0o111:
raise SystemExit(f"source-map guard is not executable: {path}")
for test_entry in ["test-native.sh", "test-linuxcnc-source-link.sh", "build-wasm.sh"]:
text = Path(test_entry).read_text(encoding="utf-8")
for guard in sorted({path for paths in expected.values() for path in paths if path.startswith("check-")}):
if f"./{guard}" not in text:
raise SystemExit(f"{test_entry} does not run workstream source-map guard: {guard}")
PY

View File

@@ -234,6 +234,41 @@ void CanonEventSink::set_xy_rotation(double angle_degrees, int line) {
}
void CanonEventSink::use_length_units(double scale, int line) {
use_length_units(scale, line, false);
}
void CanonEventSink::use_length_units(double scale, int line, bool rescale_linear_state) {
if (rescale_linear_state && unit_scale_ != 0.0 && scale != unit_scale_) {
const double factor = unit_scale_ / scale;
// LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_length_units()
// rescales linear current axes on G20/G21; A/B/C remain angular.
position_.x *= factor;
position_.y *= factor;
position_.z *= factor;
position_.u *= factor;
position_.v *= factor;
position_.w *= factor;
tool_length_offset_.x *= factor;
tool_length_offset_.y *= factor;
tool_length_offset_.z *= factor;
tool_length_offset_.u *= factor;
tool_length_offset_.v *= factor;
tool_length_offset_.w *= factor;
probe_position_.x *= factor;
probe_position_.y *= factor;
probe_position_.z *= factor;
probe_position_.u *= factor;
probe_position_.v *= factor;
probe_position_.w *= factor;
if (has_pending_motion_start_) {
pending_motion_start_.x *= factor;
pending_motion_start_.y *= factor;
pending_motion_start_.z *= factor;
pending_motion_start_.u *= factor;
pending_motion_start_.v *= factor;
pending_motion_start_.w *= factor;
}
}
unit_scale_ = scale;
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_UNITS, line);
event.feed = scale;

View File

@@ -29,6 +29,7 @@ public:
void set_xy_rotation(double angle_degrees, int line);
void use_length_units(double scale, int line);
void use_length_units(double scale, int line, bool rescale_linear_state);
void select_plane(int plane, int line);
void set_traverse_rate(double rate);
void set_feed_rate(double feed, int line);

View File

@@ -193,7 +193,7 @@ void INIT_CANON() {
void USE_LENGTH_UNITS(CANON_UNITS units) {
trace_call("USE_LENGTH_UNITS");
if (active_sink) {
active_sink->use_length_units(units_to_scale(units), event_line());
active_sink->use_length_units(units_to_scale(units), event_line(), true);
}
}
@@ -211,10 +211,10 @@ void SET_FEED_RATE(double rate) {
}
}
void SET_SPINDLE_SPEED(int, double speed) {
void SET_SPINDLE_SPEED(int spindle, double speed) {
trace_call("SET_SPINDLE_SPEED");
if (active_sink) {
active_sink->set_spindle_speed(speed, event_line());
active_sink->set_spindle_speed(spindle, speed, event_line());
}
}
@@ -402,6 +402,15 @@ void SET_MOTION_CONTROL_MODE(CANON_MOTION_MODE mode, double tolerance) {
void SET_NAIVECAM_TOLERANCE(double tolerance) {
naivecam_tolerance = tolerance;
if (active_sink) {
CncSimEvent event{};
event.version = 1;
event.type = CNC_SIM_EVENT_COMMENT;
event.line = event_line();
event.feed = tolerance;
event.reserved = 641;
active_sink->emit_raw(event);
}
}
void SET_CUTTER_RADIUS_COMPENSATION(double) {
@@ -477,21 +486,21 @@ void SET_SPINDLE_MODE(int spindle, double mode) {
void SPINDLE_RETRACT_TRAVERSE() {
}
void START_SPINDLE_CLOCKWISE(int, int) {
void START_SPINDLE_CLOCKWISE(int spindle, int) {
if (active_sink) {
active_sink->start_spindle(1, event_line());
active_sink->start_spindle(spindle, 1, event_line());
}
}
void START_SPINDLE_COUNTERCLOCKWISE(int, int) {
void START_SPINDLE_COUNTERCLOCKWISE(int spindle, int) {
if (active_sink) {
active_sink->start_spindle(-1, event_line());
active_sink->start_spindle(spindle, -1, event_line());
}
}
void STOP_SPINDLE_TURNING(int) {
void STOP_SPINDLE_TURNING(int spindle) {
if (active_sink) {
active_sink->stop_spindle(event_line());
active_sink->stop_spindle(spindle, event_line());
}
}
@@ -773,11 +782,16 @@ int GET_EXTERNAL_FLOOD() {
}
CANON_UNITS GET_EXTERNAL_LENGTH_UNIT_TYPE() {
return active_sink && active_sink->unit_scale() == 25.4 ? CANON_UNITS_INCHES : CANON_UNITS_MM;
// LinuxCNC src/emc/task/emccanon.cc derives this from INI machine/user
// units, not from the active G20/G21 program units.
return CANON_UNITS_MM;
}
double GET_EXTERNAL_LENGTH_UNITS() {
return active_sink ? active_sink->unit_scale() : 1.0;
// LinuxCNC src/emc/task/emccanon.cc returns traj.linearUnits here:
// external/user units per internal millimeter. The simulator has no INI
// machine-units bridge yet, so the source-linked default is metric.
return 1.0;
}
double GET_EXTERNAL_ANGLE_UNITS() {

View File

@@ -160,6 +160,7 @@ int main() {
bool saw_adaptive_feed_off = false;
bool saw_feed_hold_on = false;
bool saw_feed_hold_off = false;
bool saw_naivecam_tolerance = false;
bool saw_speed_feed_sync_start = false;
bool saw_speed_feed_sync_stop = false;
bool saw_arc = false;
@@ -284,9 +285,13 @@ int main() {
event.reserved == 53 &&
event.feed == 1.0);
saw_feed_hold_off = saw_feed_hold_off || (event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 29 &&
event.reserved == 53 &&
event.feed == 0.0);
event.line == 29 &&
event.reserved == 53 &&
event.feed == 0.0);
saw_naivecam_tolerance = saw_naivecam_tolerance || (event.type == CNC_SIM_EVENT_COMMENT &&
event.line == 30 &&
event.reserved == 641 &&
event.feed == 0.025);
saw_speed_feed_sync_start = saw_speed_feed_sync_start || (event.type == CNC_SIM_EVENT_COMMENT &&
event.reserved == 3301 &&
event.feed == 1.25 &&
@@ -387,6 +392,7 @@ int main() {
ok &= expect(external_motion_mode, "expected external motion-control mode query to reflect SET_MOTION_CONTROL_MODE");
ok &= expect(external_motion_tolerance, "expected external motion-control tolerance query to reflect SET_MOTION_CONTROL_MODE");
ok &= expect(external_naivecam_tolerance, "expected external naivecam tolerance query to reflect SET_NAIVECAM_TOLERANCE");
ok &= expect(saw_naivecam_tolerance, "expected naivecam tolerance state event");
ok &= expect(external_block_delete_on, "expected block-delete query to reflect enable");
ok &= expect(external_block_delete_off, "expected block-delete query to reflect disable");
ok &= expect(external_optional_stop_on, "expected optional-stop query to reflect enable");

View File

@@ -85,6 +85,18 @@ struct _inittab {
PyObject *(*initfunc)(void);
};
struct PyStatus {
int _type;
const char *func;
const char *err_msg;
int exitcode;
};
struct PyConfig {
wchar_t *program_name;
int buffered_stdio;
};
using PyCFunction = PyObject *(*)(PyObject *, PyObject *);
using getter = PyObject *(*)(PyObject *, void *);
using setter = int (*)(PyObject *, PyObject *, void *);
@@ -146,6 +158,7 @@ void Py_DecRef(PyObject *);
int PyArg_ParseTuple(PyObject *, const char *, ...);
int PyArg_VaParse(PyObject *, const char *, va_list);
PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...);
PyObject *PyObject_Call(PyObject *, PyObject *, PyObject *);
PyObject *PyObject_GetAttrString(PyObject *, const char *);
int PyObject_SetAttrString(PyObject *, const char *, PyObject *);
int PyObject_IsTrue(PyObject *);
@@ -165,6 +178,17 @@ Py_ssize_t PySequence_Length(PyObject *);
PyObject *Py_BuildValue(const char *, ...);
PyObject *PyModule_Create(PyModuleDef *);
int PyModule_AddObject(PyObject *, const char *, PyObject *);
int PyImport_ExtendInittab(_inittab *);
PyObject *PyImport_GetModuleDict(void);
PyObject *PyImport_AddModule(const char *);
int PyDict_SetItemString(PyObject *, const char *, PyObject *);
void PyConfig_InitPythonConfig(PyConfig *);
PyStatus PyConfig_SetString(PyConfig *, wchar_t **, const wchar_t *);
void PyConfig_Clear(PyConfig *);
PyStatus Py_InitializeFromConfig(PyConfig *);
wchar_t *Py_DecodeLocale(const char *, std::size_t *);
int PyRun_SimpleString(const char *);
const char *Py_GetVersion(void);
int PyType_Ready(PyTypeObject *);
PyObject *PyType_GenericNew(PyTypeObject *, PyObject *, PyObject *);
void *PyObject_NewShim(std::size_t, PyTypeObject *);

View File

@@ -1,14 +1,30 @@
#include "cnc_sim_api.h"
#include "boost/python/object.hpp"
#include "hal.h"
#include "libnml/nml/stat_msg.hh"
#include <cmath>
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <wordexp.h>
extern int _task;
extern "C" {
extern PyObject *PyInit_interpreter(void);
extern PyObject *PyInit_emccanon(void);
extern struct _inittab builtin_modules[];
extern int rtapi_snprintf(char *buf, unsigned long size, const char *fmt, ...);
extern int rtapi_vsnprintf(char *buf, unsigned long size, const char *fmt, va_list ap);
}
// LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides read(),
// execute(), and wordexp() paths; interp_convert.cc converts G0, G2, and M30.
// src/emc/rs274ngc/interp_namedparams.cc calls the HAL lookup functions
// declared in include/hal.h and implemented by src/hal/hal_lib.c.
namespace {
int collect_event(const CncSimEvent *event, void *user_data) {
@@ -57,6 +73,119 @@ bool wordexp_expands_environment_variables() {
return ok;
}
bool wordexp_expands_braced_environment_variables() {
setenv("CNC_SIM_WORDEXP_BRACED_PROBE", "linuxcnc-braced-wordexp", 1);
wordexp_t expanded{};
if (wordexp("${CNC_SIM_WORDEXP_BRACED_PROBE}/subroutines", &expanded, 0) != 0) {
return false;
}
const bool ok = expanded.we_wordc == 1 &&
expanded.we_wordv &&
expanded.we_wordv[0] &&
std::strcmp(expanded.we_wordv[0], "linuxcnc-braced-wordexp/subroutines") == 0;
wordfree(&expanded);
return ok;
}
bool hal_lookup_is_unavailable() {
if (hal_init("cnc_sim_wasm_runtime_probe") != 1) {
return false;
}
if (hal_ready(1) != 0) {
return false;
}
hal_data_u dummy{};
hal_data_u *data = &dummy;
hal_type_t type = HAL_FLOAT;
bool connected = true;
if (hal_get_pin_value_by_name("cnc-sim.missing-pin", &type, &data, &connected) != -1 ||
type != HAL_TYPE_UNINITIALIZED ||
data != nullptr ||
connected) {
return false;
}
data = &dummy;
type = HAL_FLOAT;
bool has_writers = true;
if (hal_get_signal_value_by_name("cnc-sim.missing-signal", &type, &data, &has_writers) != -1 ||
type != HAL_TYPE_UNINITIALIZED ||
data != nullptr ||
has_writers) {
return false;
}
data = &dummy;
type = HAL_FLOAT;
if (hal_get_param_value_by_name("cnc-sim.missing-param", &type, &data) != -1 ||
type != HAL_TYPE_UNINITIALIZED ||
data != nullptr) {
return false;
}
return true;
}
bool nml_status_constructors_match_linuxcnc() {
// LinuxCNC source basis: src/libnml/nml/nmlmsg.cc and stat_msg.cc
// initialize RCS status messages before EMC status objects use them.
RCS_GENERIC_STATUS status;
return status._type == RCS_GENERIC_STATUS_TYPE &&
status.size == static_cast<long>(sizeof(RCS_GENERIC_STATUS)) &&
status.command_type == -1 &&
status.echo_serial_number == -1 &&
status.status == RCS_STATUS::UNINITIALIZED &&
status._state == -1;
}
bool builtin_modules_match_linuxcnc_preview_runtime() {
// LinuxCNC source basis: src/emc/rs274ngc/gcodemodule.cc defines _task=0
// for preview and registers interpreter/emccanon builtin module entries;
// src/emc/task/taskclass.cc uses the same builtin module table shape.
return _task == 0 &&
builtin_modules[0].name &&
std::strcmp(builtin_modules[0].name, "interpreter") == 0 &&
builtin_modules[0].initfunc == PyInit_interpreter &&
builtin_modules[0].initfunc() == nullptr &&
builtin_modules[1].name &&
std::strcmp(builtin_modules[1].name, "emccanon") == 0 &&
builtin_modules[1].initfunc == PyInit_emccanon &&
builtin_modules[1].initfunc() == nullptr &&
builtin_modules[2].name == nullptr &&
builtin_modules[2].initfunc == nullptr;
}
int call_rtapi_vsnprintf(char *buffer, unsigned long size, const char *format, ...) {
va_list args;
va_start(args, format);
const int result = rtapi_vsnprintf(buffer, size, format, args);
va_end(args);
return result;
}
bool rtapi_formatting_matches_linuxcnc_userspace() {
// LinuxCNC source basis: src/rtapi/uspace_common.h implements both
// rtapi_snprintf() and rtapi_vsnprintf() as userspace vsnprintf() wrappers.
char formatted[32]{};
if (rtapi_snprintf(formatted, sizeof(formatted), "HAL_%s_%d", "probe", 7) != 11 ||
std::strcmp(formatted, "HAL_probe_7") != 0) {
return false;
}
char via_va_list[32]{};
if (call_rtapi_vsnprintf(via_va_list, sizeof(via_va_list), "%s.%02d", "axis", 3) != 7 ||
std::strcmp(via_va_list, "axis.03") != 0) {
return false;
}
char truncated[5]{};
return rtapi_snprintf(truncated, sizeof(truncated), "abcdef") == 6 &&
std::strcmp(truncated, "abcd") == 0;
}
} // namespace
int main() {
@@ -66,6 +195,21 @@ int main() {
if (!wordexp_expands_environment_variables()) {
return 6;
}
if (!wordexp_expands_braced_environment_variables()) {
return 8;
}
if (!hal_lookup_is_unavailable()) {
return 7;
}
if (!nml_status_constructors_match_linuxcnc()) {
return 9;
}
if (!builtin_modules_match_linuxcnc_preview_runtime()) {
return 10;
}
if (!rtapi_formatting_matches_linuxcnc_userspace()) {
return 11;
}
std::vector<CncSimEvent> events;
CncSimHandle *sim = cnc_sim_create();

View File

@@ -1,8 +1,38 @@
#include "dlfcn.h"
#include "interp_base.hh"
#include <cstring>
// LinuxCNC source basis: src/emc/rs274ngc/interp_base.cc provides
// interp_from_shlib(), with the declaration in interp_base.hh.
// interp_from_shlib(), with the declaration in interp_base.hh. Its native
// dlopen()/dlsym() loader edge is intentionally unavailable in WASM probes.
int main() {
InterpBase *interp = interp_from_shlib("nonexistent-interpreter.so");
return interp == nullptr ? 0 : 1;
if (dlopen(nullptr, RTLD_GLOBAL) != nullptr) {
return 2;
}
if (dlopen("/tmp/cnc_sim_missing_interpreter.so", RTLD_NOW) != nullptr) {
return 3;
}
if (dlsym(nullptr, "makeInterp") != nullptr) {
return 4;
}
const char *error = dlerror();
if (!error || !std::strstr(error, "dynamic loading is unavailable")) {
return 5;
}
if (dlclose(nullptr) != 0) {
return 6;
}
// LinuxCNC src/emc/rs274ngc/interp_base.cc uses absolute interpreter
// paths directly before calling dlopen().
if (interp_from_shlib("/tmp/cnc_sim_missing_absolute_interpreter.so") != nullptr) {
return 7;
}
// Relative interpreter names are resolved through EMC2_HOME/lib/linuxcnc
// before the same unavailable dynamic-loading edge is reached.
if (interp_from_shlib("nonexistent-interpreter.so") != nullptr) {
return 8;
}
return 0;
}

View File

@@ -71,3 +71,10 @@ RCS_STAT_MSG::RCS_STAT_MSG(NMLTYPE t, size_t sz) : NMLmsg(t, sz) {
status = RCS_STATUS::UNINITIALIZED;
_state = -1;
}
RCS_GENERIC_STATUS::RCS_GENERIC_STATUS()
: RCS_STAT_MSG(RCS_GENERIC_STATUS_TYPE, sizeof(RCS_GENERIC_STATUS)) {
}
void RCS_GENERIC_STATUS::update(CMS *) {
}

View File

@@ -0,0 +1,252 @@
#include "boost/python/class.hpp"
#include "boost/python/converter/registry.hpp"
#include "boost/python/def.hpp"
#include "boost/python/dict.hpp"
#include "boost/python/enum.hpp"
#include "boost/python/exception_translator.hpp"
#include "boost/python/extract.hpp"
#include "boost/python/import.hpp"
#include "boost/python/list.hpp"
#include "boost/python/module.hpp"
#include "boost/python/object.hpp"
#include "boost/python/object/class_detail.hpp"
#include "boost/python/return_internal_reference.hpp"
#include "boost/python/scope.hpp"
#include "boost/python/str.hpp"
#include "boost/python/suite/indexing/map_indexing_suite.hpp"
#include "boost/python/tuple.hpp"
#include <cstdlib>
#include <cstring>
#include <map>
#include <string>
// LinuxCNC source basis: src/emc/rs274ngc/gcodemodule.cc,
// interpmodule.cc, py*.cc, interp_python.cc, interp_namedparams.cc, and
// src/emc/pythonplugin/python_plugin.cc use these Python C API and
// Boost.Python entry points. The WASM shim keeps the API inert and link-safe
// while browser Python execution remains disabled.
namespace {
struct ProbeObject {
PyObject_HEAD
int value;
};
struct ProbeCppObject {
int value = 0;
};
PyObject *probe_getter(PyObject *, void *) {
return Py_None;
}
int probe_setter(PyObject *, PyObject *, void *) {
return 0;
}
PyObject *probe_method(PyObject *, PyObject *) {
return Py_None;
}
void translate_probe_exception(const std::runtime_error &) {
}
bool boost_python_shims_are_inert() {
namespace bp = boost::python;
bp::object object;
if (object.ptr() != nullptr || object.attr("missing").operator bp::object().ptr() != nullptr ||
object["missing"].ptr() != nullptr || object[0].ptr() != nullptr ||
object().ptr() != nullptr) {
return false;
}
bp::list list;
list.append(object);
list.extend(list);
bp::tuple tuple(list);
bp::dict dict;
if (bp::tuple().ptr() != nullptr || tuple.ptr() != nullptr ||
bp::make_tuple(1, 2.0, "three").ptr() != nullptr ||
dict.keys().ptr() != nullptr || bp::len(list) != 0) {
return false;
}
if ((object % tuple).ptr() != nullptr || ("linuxcnc" % tuple).ptr() != nullptr ||
(object + object).ptr() != nullptr || (object + "suffix").ptr() != nullptr ||
("prefix" + object).ptr() != nullptr) {
return false;
}
bp::scope scope(object);
scope.attr("this") = object;
if (scope.attr("this").operator bp::object().ptr() != nullptr) {
return false;
}
if (bp::getattr(object, "__next__").ptr() != nullptr ||
bp::make_function(probe_method).ptr() != nullptr ||
bp::make_getter(probe_getter).ptr() != nullptr ||
bp::make_setter(probe_setter).ptr() != nullptr ||
bp::import("traceback").ptr() != nullptr ||
bp::str("\n").join(object).ptr() != nullptr) {
return false;
}
if (static_cast<int>(bp::extract<int>(object)) != 0 ||
static_cast<double>(bp::extract<double>(object)) != 0.0 ||
static_cast<std::string>(bp::extract<std::string>(object)) != std::string() ||
static_cast<char *>(bp::extract<char *>(object)) != nullptr) {
return false;
}
bp::handle<> default_handle;
bp::handle<> null_handle(bp::allow_null(static_cast<PyObject *>(nullptr)));
bp::handle<> borrowed_handle(bp::borrowed(Py_None));
if (default_handle || default_handle.get() != nullptr ||
null_handle || null_handle.get() != nullptr ||
borrowed_handle || borrowed_handle.get() != nullptr) {
return false;
}
bp::handle_exception();
bp::class_<ProbeCppObject>("ProbeCppObject")
.def("method", probe_method)
.def_readwrite("value", &ProbeCppObject::value)
.add_property("property", bp::make_getter(probe_getter), bp::make_setter(probe_setter));
bp::class_<ProbeCppObject>("ProbeCppObjectNoInit", bp::no_init);
bp::class_<ProbeCppObject>("ProbeCppObjectDoc", "doc");
bp::class_<ProbeCppObject>("ProbeCppObjectDocNoInit", "doc", bp::no_init);
bp::enum_<int>("ProbeEnum").value("one", 1).export_values();
bp::def("probe", probe_method);
bp::register_exception_translator<std::runtime_error>(translate_probe_exception);
bp::return_internal_reference<1, bp::default_call_policies> reference_policy;
(void)reference_policy;
bp::map_indexing_suite<std::map<int, int>> map_suite;
(void)map_suite;
if (bp::objects::registered_class_object(bp::type_id<ProbeCppObject>()) != nullptr) {
return false;
}
return true;
}
} // namespace
BOOST_PYTHON_MODULE(cnc_sim_python_c_api_probe_module) {
}
int main() {
if (Py_IsInitialized() != 1) {
return 1;
}
if (PyErr_Occurred() != nullptr) {
return 2;
}
PyObject *exc = reinterpret_cast<PyObject *>(1);
PyObject *val = reinterpret_cast<PyObject *>(1);
PyObject *tb = reinterpret_cast<PyObject *>(1);
PyErr_Fetch(&exc, &val, &tb);
if (exc || val || tb) {
return 3;
}
if (!Py_None || !Py_TYPE(Py_None) || std::strcmp(Py_TYPE(Py_None)->tp_name, "NoneType") != 0) {
return 4;
}
if (PyUnicode_FromString("linuxcnc") != Py_None) {
return 5;
}
if (PyObject_Str(Py_None) != Py_None) {
return 16;
}
if (std::strcmp(PyUnicode_AsUTF8(Py_None), "") != 0) {
return 6;
}
if (PyUnicode_Check(Py_None) != 0 || PyLong_Check(Py_None) != 0 || PyFloat_Check(Py_None) != 0 ||
PyGen_Check(Py_None) != 0 || PyErr_ExceptionMatches(PyExc_KeyError) != 0) {
return 17;
}
if (PyLong_FromLong(7) != Py_None || PyLong_AsLong(Py_None) != 0) {
return 7;
}
if (PyFloat_AsDouble(Py_None) != 0.0) {
return 8;
}
if (PyTuple_New(2) != Py_None || PyTuple_SetItem(Py_None, 0, Py_None) != 0 || PyTuple_Size(Py_None) != 0) {
return 9;
}
if (PyList_New(2) != Py_None || PyList_SetItem(Py_None, 0, Py_None) != 0 || PyList_Size(Py_None) != 0) {
return 10;
}
if (PyTuple_GetItem(Py_None, 0) != Py_None || PyList_GetItem(Py_None, 0) != Py_None ||
PySequence_GetItem(Py_None, 0) != Py_None || PySequence_Length(Py_None) != 0) {
return 18;
}
if (PyObject_IsTrue(Py_None) != 0 || PyCallable_Check(Py_None) != 0 || PyObject_IsInstance(Py_None, Py_None) != 0) {
return 11;
}
if (PyObject_CallMethod(Py_None, const_cast<char *>("noop"), const_cast<char *>("")) != Py_None ||
PyObject_Call(Py_None, Py_None, Py_None) != Py_None ||
PyObject_GetAttrString(Py_None, "noop") != Py_None ||
PyObject_SetAttrString(Py_None, "noop", Py_None) != 0) {
return 19;
}
if (PyArg_ParseTuple(Py_None, "") != 0 || Py_BuildValue("i", 1) != Py_None) {
return 20;
}
ProbeObject *probe = PyObject_New(ProbeObject, &PyList_Type);
if (!probe || Py_TYPE(reinterpret_cast<PyObject *>(probe)) != &PyList_Type) {
std::free(probe);
return 12;
}
std::free(probe);
if (PyModule_Create(nullptr) != Py_None || PyModule_AddObject(Py_None, "value", Py_None) != 0) {
return 13;
}
_inittab inittab[] = {
{"interpreter", nullptr},
{nullptr, nullptr},
};
if (PyImport_ExtendInittab(inittab) != 0 || PyImport_GetModuleDict() == Py_None ||
PyImport_AddModule("interpreter") == Py_None ||
PyDict_SetItemString(PyImport_GetModuleDict(), "interpreter", PyImport_AddModule("interpreter")) != 0) {
return 21;
}
PyConfig config{};
PyConfig_InitPythonConfig(&config);
if (config.program_name != nullptr || config.buffered_stdio != 1) {
return 22;
}
const wchar_t program_name[] = L"linuxcnc";
PyStatus config_status = PyConfig_SetString(&config, &config.program_name, program_name);
if (config_status._type != 0 || config.program_name != program_name) {
return 23;
}
PyStatus init_status = Py_InitializeFromConfig(&config);
if (init_status._type != 0) {
return 24;
}
std::size_t decoded_size = 99;
if (Py_DecodeLocale("linuxcnc", &decoded_size) != nullptr || decoded_size != 8) {
return 25;
}
if (PyRun_SimpleString("pass") != 0 || std::strcmp(Py_GetVersion(), "wasm-python-disabled") != 0) {
return 26;
}
PyConfig_Clear(&config);
if (config.program_name != nullptr || config.buffered_stdio != 0) {
return 27;
}
if (PyType_Ready(&PyList_Type) != 0 || PyType_GenericNew(&PyList_Type, Py_None, Py_None) != Py_None) {
return 14;
}
if (PyErr_Format(PyExc_TypeError, "linuxcnc") != nullptr) {
return 15;
}
if (!boost_python_shims_are_inert()) {
return 28;
}
return 0;
}

View File

@@ -1,6 +1,7 @@
#include "boost/python/object.hpp"
#include <cstdlib>
#include <cstring>
// LinuxCNC source basis: src/emc/rs274ngc/interp_python.cc,
// interp_namedparams.cc, gcodemodule.cc, and src/emc/pythonplugin/python_plugin.cc
@@ -8,7 +9,11 @@
extern "C" {
static PyTypeObject none_type = {"NoneType"};
static PyTypeObject module_type = {"module"};
static PyTypeObject dict_type = {"dict"};
static PyObject none_object = {&none_type};
static PyObject module_object = {&module_type};
static PyObject dict_object = {&dict_type};
PyObject *PyExc_KeyError = reinterpret_cast<PyObject *>(1);
PyObject *PyExc_KeyboardInterrupt = reinterpret_cast<PyObject *>(2);
@@ -111,6 +116,10 @@ PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...) {
return &none_object;
}
PyObject *PyObject_Call(PyObject *, PyObject *, PyObject *) {
return &none_object;
}
PyObject *PyObject_GetAttrString(PyObject *, const char *) {
return &none_object;
}
@@ -187,6 +196,64 @@ int PyModule_AddObject(PyObject *, const char *, PyObject *) {
return 0;
}
int PyImport_ExtendInittab(_inittab *) {
return 0;
}
PyObject *PyImport_GetModuleDict(void) {
return &dict_object;
}
PyObject *PyImport_AddModule(const char *) {
return &module_object;
}
int PyDict_SetItemString(PyObject *, const char *, PyObject *) {
return 0;
}
void PyConfig_InitPythonConfig(PyConfig *config) {
if (!config) {
return;
}
config->program_name = nullptr;
config->buffered_stdio = 1;
}
PyStatus PyConfig_SetString(PyConfig *, wchar_t **config_str, const wchar_t *value) {
if (config_str) {
*config_str = const_cast<wchar_t *>(value);
}
return PyStatus{0, nullptr, nullptr, 0};
}
void PyConfig_Clear(PyConfig *config) {
if (!config) {
return;
}
config->program_name = nullptr;
config->buffered_stdio = 0;
}
PyStatus Py_InitializeFromConfig(PyConfig *) {
return PyStatus{0, nullptr, nullptr, 0};
}
wchar_t *Py_DecodeLocale(const char *arg, std::size_t *size) {
if (size) {
*size = arg ? std::strlen(arg) : 0;
}
return nullptr;
}
int PyRun_SimpleString(const char *) {
return 0;
}
const char *Py_GetVersion(void) {
return "wasm-python-disabled";
}
int PyType_Ready(PyTypeObject *) {
return 0;
}

View File

@@ -1,7 +1,13 @@
#include "pythonplugin/python_plugin.hh"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits.h>
#include <string>
#include <vector>
#include <unistd.h>
// LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc provides the
// native PythonPlugin implementation called by RS274 Python/remap paths.
@@ -11,10 +17,139 @@ __attribute__((weak)) std::string handle_pyerror() {
PythonPlugin *python_plugin = nullptr;
PythonPlugin *PythonPlugin::instantiate(struct _inittab *) {
namespace {
std::string trim_ini_value(std::string value) {
const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
});
const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
}).base();
if (first >= last) {
return {};
}
return std::string(first, last);
}
bool read_ini_section(const std::string &text, std::string &section_name) {
if (text.empty() || text.front() != '[') {
return false;
}
const auto close = text.find(']');
if (close == std::string::npos) {
return false;
}
// LinuxCNC source basis: IniFile::processLine() trims the section name
// between '[' and ']' and ignores text after the closing bracket.
section_name = trim_ini_value(text.substr(1, close - 1));
return true;
}
std::vector<std::string> find_ini_values(const char *iniFilename,
const char *section,
const char *key) {
std::vector<std::string> values;
FILE *file = std::fopen(iniFilename, "r");
if (!file) {
return values;
}
bool in_section = false;
char line[PATH_MAX + 256];
while (std::fgets(line, sizeof(line), file)) {
std::string text = trim_ini_value(line);
if (text.empty() || text[0] == ';' || text[0] == '#') {
continue;
}
std::string section_name;
if (read_ini_section(text, section_name)) {
in_section = section_name == section;
continue;
}
if (!in_section) {
continue;
}
const auto equals = text.find('=');
if (equals == std::string::npos) {
continue;
}
if (trim_ini_value(text.substr(0, equals)) == key) {
values.push_back(trim_ini_value(text.substr(equals + 1)));
}
}
std::fclose(file);
return values;
}
bool find_ini_value(const char *iniFilename,
const char *section,
const char *key,
std::string &value) {
const std::vector<std::string> values = find_ini_values(iniFilename, section, key);
if (values.empty()) {
return false;
}
value = values.front();
return true;
}
bool tilde_expand_path(const std::string &path, std::string &expanded) {
// LinuxCNC source basis: IniFile::tildeExpand() copies non-"~/" paths and
// returns failure for "~/" paths when HOME is unavailable.
if (path.size() < 2 || path[0] != '~' || path[1] != '/') {
expanded = path;
return true;
}
const char *home = std::getenv("HOME");
if (!home) {
return false;
}
expanded = std::string(home) + path.substr(1);
return true;
}
int check_python_path_entries(const char *iniFilename,
const char *key,
pp_status failure_status) {
for (const std::string &entry : find_ini_values(iniFilename, "PYTHON", key)) {
std::string expanded;
// LinuxCNC source basis: python_plugin.cc configure() runs
// TildeExpansion on [PYTHON] PATH_PREPEND/PATH_APPEND before
// PyRun_SimpleString(); browser-safe probes preserve the expansion
// failure status without executing Python path mutation.
if (!tilde_expand_path(entry, expanded)) {
return failure_status;
}
}
return PLUGIN_OK;
}
} // namespace
PythonPlugin *PythonPlugin::instantiate(struct _inittab *inittab) {
static PythonPlugin plugin;
python_plugin = &plugin;
python_plugin->status = PLUGIN_OK;
if (!python_plugin) {
python_plugin = &plugin;
python_plugin->status = PLUGIN_OK;
if (inittab) {
for (int i = 0; inittab[i].name != nullptr; ++i) {
// LinuxCNC source basis: python_plugin.cc reports
// PLUGIN_INITTAB_FAILED when a built-in module from the
// _inittab cannot be initialized.
if (!inittab[i].initfunc || inittab[i].initfunc() == nullptr) {
python_plugin->error_msg = "failed to initialize built-in module";
python_plugin->status = PLUGIN_INITTAB_FAILED;
break;
}
}
}
}
if (!python_plugin->usable()) {
return nullptr;
}
return python_plugin;
}
@@ -42,8 +177,35 @@ int PythonPlugin::configure(const char *iniFilename, const char *section) {
}
std::fclose(file);
status = PLUGIN_OK;
return status;
std::string toplevel;
if (find_ini_value(iniFilename, section, "TOPLEVEL", toplevel)) {
char resolved_path[PATH_MAX];
// LinuxCNC source basis: python_plugin.cc configure() expands and
// resolves [PYTHON] TOPLEVEL before initialize(); a missing path
// returns PLUGIN_BAD_PATH before Python runtime status is considered.
if (realpath(toplevel.c_str(), resolved_path) == nullptr) {
error_msg = "bad path";
status = PLUGIN_BAD_PATH;
return status;
}
}
status = check_python_path_entries(iniFilename,
"PATH_PREPEND",
PLUGIN_EXCEPTION_DURING_PATH_PREPEND);
if (status != PLUGIN_OK) {
error_msg = "bad path prepend";
return status;
}
status = check_python_path_entries(iniFilename,
"PATH_APPEND",
PLUGIN_EXCEPTION_DURING_PATH_APPEND);
if (status != PLUGIN_OK) {
error_msg = "bad path append";
return status;
}
return initialize();
}
bool PythonPlugin::is_callable(const char *, const char *) {
@@ -58,6 +220,9 @@ int PythonPlugin::call(const char *,
if (!callable) {
return PLUGIN_NO_CALLABLE;
}
if (status < PLUGIN_OK) {
return status;
}
retval = boost::python::object();
exception_msg = handle_pyerror();
@@ -72,12 +237,21 @@ int PythonPlugin::run_string(const char *, boost::python::object &retval, bool)
}
int PythonPlugin::call_method(boost::python::object, boost::python::object &retval) {
// LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc returns the
// existing negative plugin status before invoking a Python callable.
if (status < PLUGIN_OK) {
return status;
}
retval = boost::python::object();
status = PLUGIN_OK;
return status;
}
int PythonPlugin::initialize() {
status = PLUGIN_OK;
// LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc sets
// PLUGIN_PYTHON_NOT_INITIALIZED when initialize() runs without an active
// Python runtime. Browser-safe probes keep Python execution disabled.
error_msg = "python disabled in wasm-safe probe";
status = PLUGIN_PYTHON_NOT_INITIALIZED;
return status;
}

View File

@@ -0,0 +1,39 @@
#include "pythonplugin/python_plugin.hh"
namespace {
PyObject *init_bad_module() {
return nullptr;
}
} // namespace
// LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc
// PythonPlugin::PythonPlugin(struct _inittab*) reports PLUGIN_INITTAB_FAILED
// when a built-in Python module from the first caller's inittab cannot be
// initialized; instantiate() then returns nullptr for the unusable plugin.
int main() {
_inittab bad_modules[] = {
{"cnc_sim_bad_module", init_bad_module},
{nullptr, nullptr},
};
PythonPlugin *plugin = PythonPlugin::instantiate(bad_modules);
if (plugin != nullptr) {
return 1;
}
if (python_plugin == nullptr) {
return 2;
}
if (python_plugin->plugin_status() != PLUGIN_INITTAB_FAILED) {
return 3;
}
if (python_plugin->usable()) {
return 4;
}
if (python_plugin->last_errmsg() != "failed to initialize built-in module") {
return 5;
}
return 0;
}

View File

@@ -1,5 +1,9 @@
#include "pythonplugin/python_plugin.hh"
#include <string>
#include <cstdlib>
#include <cstdio>
// LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and
// python_plugin.hh define the PythonPlugin API exercised by this link probe.
int main() {
@@ -13,13 +17,233 @@ int main() {
if (plugin->configure(nullptr, nullptr) != PLUGIN_NO_SECTION) {
return 6;
}
if (plugin->usable()) {
return 26;
}
if (plugin->last_errmsg() != "no section") {
return 23;
}
if (plugin->configure("/tmp/cnc_sim_missing_python_plugin_probe.ini", "PYTHON") != PLUGIN_BAD_INIFILE) {
return 7;
}
if (plugin->usable()) {
return 27;
}
boost::python::object retval;
if (plugin->call(nullptr, "noop", boost::python::object(), boost::python::object(), retval) != PLUGIN_BAD_INIFILE) {
return 14;
}
if (plugin->is_callable(nullptr, "noop")) {
return 28;
}
if (plugin->call_method(boost::python::object(), retval) != PLUGIN_BAD_INIFILE) {
return 15;
}
if (plugin->run_string("noop", retval, false) != PLUGIN_OK) {
return 3;
}
if (!plugin->usable()) {
return 29;
}
unsetenv("INI_FILE_NAME");
if (plugin->configure(nullptr, "PYTHON") != PLUGIN_NO_INIFILE) {
return 21;
}
if (plugin->last_errmsg() != "no inifile") {
return 24;
}
const char *ini = "/tmp/cnc_sim_python_plugin_probe.ini";
{
FILE *file = std::fopen(ini, "w");
if (!file) {
return 16;
}
std::fputs("[PYTHON]\n", file);
std::fclose(file);
}
const char *bad_path_ini = "/tmp/cnc_sim_python_plugin_probe_bad_path.ini";
{
FILE *file = std::fopen(bad_path_ini, "w");
if (!file) {
std::remove(ini);
return 33;
}
std::fputs("[PYTHON]\nTOPLEVEL = /tmp/cnc_sim_missing_python_toplevel.py\n", file);
std::fclose(file);
}
if (plugin->configure(bad_path_ini, "PYTHON") != PLUGIN_BAD_PATH) {
std::remove(ini);
std::remove(bad_path_ini);
return 34;
}
if (plugin->usable()) {
std::remove(ini);
std::remove(bad_path_ini);
return 35;
}
if (plugin->last_errmsg() != "bad path") {
std::remove(ini);
std::remove(bad_path_ini);
return 36;
}
std::remove(bad_path_ini);
const char *path_prepend_ini = "/tmp/cnc_sim_python_plugin_probe_path_prepend.ini";
{
FILE *file = std::fopen(path_prepend_ini, "w");
if (!file) {
std::remove(ini);
return 37;
}
std::fputs("[PYTHON]\nPATH_PREPEND = ~/linuxcnc-python\n", file);
std::fclose(file);
}
const char *saved_home = std::getenv("HOME");
const std::string saved_home_value = saved_home ? saved_home : "";
unsetenv("HOME");
if (plugin->configure(path_prepend_ini, "PYTHON") != PLUGIN_EXCEPTION_DURING_PATH_PREPEND) {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(path_prepend_ini);
return 38;
}
if (plugin->usable()) {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(path_prepend_ini);
return 39;
}
if (plugin->last_errmsg() != "bad path prepend") {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(path_prepend_ini);
return 40;
}
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(path_prepend_ini);
const char *spaced_section_ini = "/tmp/cnc_sim_python_plugin_probe_spaced_section.ini";
{
FILE *file = std::fopen(spaced_section_ini, "w");
if (!file) {
std::remove(ini);
return 47;
}
std::fputs("[ PYTHON ] ; section comment\nPATH_PREPEND = ~/linuxcnc-python\n", file);
std::fclose(file);
}
unsetenv("HOME");
if (plugin->configure(spaced_section_ini, "PYTHON") != PLUGIN_EXCEPTION_DURING_PATH_PREPEND) {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(spaced_section_ini);
return 48;
}
if (plugin->last_errmsg() != "bad path prepend") {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(spaced_section_ini);
return 49;
}
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(spaced_section_ini);
const char *path_append_ini = "/tmp/cnc_sim_python_plugin_probe_path_append.ini";
{
FILE *file = std::fopen(path_append_ini, "w");
if (!file) {
std::remove(ini);
return 41;
}
std::fputs("[PYTHON]\nPATH_APPEND = ~/linuxcnc-python\n", file);
std::fclose(file);
}
unsetenv("HOME");
if (plugin->configure(path_append_ini, "PYTHON") != PLUGIN_EXCEPTION_DURING_PATH_APPEND) {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(path_append_ini);
return 42;
}
if (plugin->usable()) {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(path_append_ini);
return 43;
}
if (plugin->last_errmsg() != "bad path append") {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(path_append_ini);
return 44;
}
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(path_append_ini);
const char *plain_path_ini = "/tmp/cnc_sim_python_plugin_probe_plain_path.ini";
{
FILE *file = std::fopen(plain_path_ini, "w");
if (!file) {
std::remove(ini);
return 45;
}
std::fputs("[PYTHON]\nPATH_PREPEND = /tmp\nPATH_APPEND = /tmp\n", file);
std::fclose(file);
}
if (plugin->configure(plain_path_ini, "PYTHON") != PLUGIN_PYTHON_NOT_INITIALIZED) {
std::remove(ini);
std::remove(plain_path_ini);
return 46;
}
std::remove(plain_path_ini);
setenv("INI_FILE_NAME", ini, 1);
if (plugin->configure(nullptr, "PYTHON") != PLUGIN_PYTHON_NOT_INITIALIZED) {
std::remove(ini);
return 22;
}
if (plugin->usable()) {
std::remove(ini);
return 30;
}
unsetenv("INI_FILE_NAME");
if (plugin->configure(ini, "PYTHON") != PLUGIN_PYTHON_NOT_INITIALIZED) {
std::remove(ini);
return 17;
}
std::remove(ini);
if (plugin->call(nullptr, "noop", boost::python::object(), boost::python::object(), retval) != PLUGIN_PYTHON_NOT_INITIALIZED) {
return 18;
}
if (plugin->call_method(boost::python::object(), retval) != PLUGIN_PYTHON_NOT_INITIALIZED) {
return 19;
}
if (plugin->is_callable(nullptr, "noop")) {
return 31;
}
if (plugin->run_string("noop", retval, false) != PLUGIN_OK) {
return 20;
}
if (plugin->is_callable(nullptr, nullptr)) {
return 32;
}
if (plugin->call(nullptr, nullptr, boost::python::object(), boost::python::object(), retval) != PLUGIN_NO_CALLABLE) {
return 8;
}
@@ -35,6 +259,9 @@ int main() {
if (plugin->last_exception().empty()) {
return 12;
}
if (plugin->last_exception() != handle_pyerror()) {
return 25;
}
if (plugin->run_string("noop", retval, false) != PLUGIN_OK) {
return 13;
}

View File

@@ -2,7 +2,8 @@
#include <cstdio>
// LinuxCNC source basis: src/rtapi/uspace_common.h provides rtapi_snprintf()
// for userspace RTAPI formatting; src/rtapi/rtapi.h declares the API.
// and rtapi_vsnprintf() for userspace RTAPI formatting; src/rtapi/rtapi.h
// declares both APIs.
extern "C" int rtapi_snprintf(char *buf, unsigned long size, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
@@ -10,3 +11,7 @@ extern "C" int rtapi_snprintf(char *buf, unsigned long size, const char *fmt, ..
va_end(ap);
return rc;
}
extern "C" int rtapi_vsnprintf(char *buf, unsigned long size, const char *fmt, va_list ap) {
return std::vsnprintf(buf, static_cast<std::size_t>(size), fmt, ap);
}

View File

@@ -89,6 +89,42 @@ int main() {
return 9;
}
tooldata_init(false);
std::string nonrandom_path_template = make_temp_template("cnc_tooldata_common_nonrandom_probe_XXXXXX");
std::vector<char> nonrandom_path(nonrandom_path_template.begin(), nonrandom_path_template.end());
nonrandom_path.push_back('\0');
const int nonrandom_fd = mkstemp(nonrandom_path.data());
if (nonrandom_fd < 0) {
return 16;
}
FILE *nonrandom_fp = fdopen(nonrandom_fd, "w");
if (!nonrandom_fp) {
close(nonrandom_fd);
std::remove(nonrandom_path.data());
return 17;
}
std::fputs("T-1 P5 Z4.25\n", nonrandom_fp);
std::fclose(nonrandom_fp);
if (tooldata_load(nonrandom_path.data()) != 0) {
std::remove(nonrandom_path.data());
return 18;
}
std::remove(nonrandom_path.data());
// LinuxCNC source basis: src/emc/tooldata/tooldata_common.cc
// tooldata_load() mirrors a matching nonrandom table entry into spindle
// pocket 0 after tooldata_read_entry() assigns fake pocket indices.
CANON_TOOL_TABLE nonrandom_spindle = tooldata_entry_init();
if (tooldata_get(&nonrandom_spindle, 0) != IDX_OK) {
return 19;
}
if (nonrandom_spindle.toolno != -1 || nonrandom_spindle.pocketno != 5 ||
nonrandom_spindle.offset.tran.z != 4.25) {
return 20;
}
tooldata_init(true);
tooldata_set_db(DB_ACTIVE);
if (tooldata_load(path.data()) != -1) {
return 10;

View File

@@ -84,6 +84,71 @@ int main() {
return 45;
}
const pid_t nonrandom_t0_child = fork();
if (nonrandom_t0_child < 0) {
return 64;
}
if (nonrandom_t0_child == 0) {
if (tool_mmap_creator(nullptr, 0) != 0) {
_exit(65);
}
CANON_TOOL_TABLE nonrandom_t0 = tooldata_entry_init();
nonrandom_t0.toolno = 0;
nonrandom_t0.pocketno = 12;
if (tooldata_put(nonrandom_t0, 12) == IDX_FAIL) {
_exit(66);
}
// LinuxCNC src/emc/tooldata/tooldata_mmap.cc returns pocket 0 for T0
// before scanning table entries when the toolchanger is not random.
if (tooldata_find_index_for_tool(0) != 0) {
_exit(67);
}
_exit(0);
}
int nonrandom_t0_status = 0;
if (waitpid(nonrandom_t0_child, &nonrandom_t0_status, 0) != nonrandom_t0_child) {
return 68;
}
if (!WIFEXITED(nonrandom_t0_status) || WEXITSTATUS(nonrandom_t0_status) != 0) {
return 69;
}
const pid_t random_t0_child = fork();
if (random_t0_child < 0) {
return 70;
}
if (random_t0_child == 0) {
if (tool_mmap_creator(nullptr, 1) != 0) {
_exit(71);
}
tooldata_reset();
CANON_TOOL_TABLE spindle_t0 = tooldata_entry_init();
spindle_t0.toolno = 0;
spindle_t0.pocketno = 0;
if (tooldata_put(spindle_t0, 0) == IDX_FAIL) {
_exit(72);
}
CANON_TOOL_TABLE random_t0 = tooldata_entry_init();
random_t0.toolno = 0;
random_t0.pocketno = 15;
if (tooldata_put(random_t0, 15) == IDX_FAIL) {
_exit(73);
}
// LinuxCNC src/emc/tooldata/tooldata_mmap.cc continues past pocket 0
// while scanning random toolchanger entries, so a nonzero T0 pocket wins.
if (tooldata_find_index_for_tool(0) != 15) {
_exit(74);
}
_exit(0);
}
int random_t0_status = 0;
if (waitpid(random_t0_child, &random_t0_status, 0) != random_t0_child) {
return 75;
}
if (!WIFEXITED(random_t0_status) || WEXITSTATUS(random_t0_status) != 0) {
return 76;
}
if (tool_mmap_user() != -1) {
return 25;
}
@@ -113,6 +178,18 @@ int main() {
if (tooldata_db_getall() != -1) {
return 28;
}
// LinuxCNC source basis: src/emc/tooldata/tooldata_db.cc
// tooldata_db_notify() returns 0 without side effects while db_live is
// false, which is the browser-safe shim state for external DB services.
if (tooldata_db_notify(SPINDLE_LOAD, 7, 12, uncreated_tool) != 0) {
return 57;
}
if (tooldata_db_notify(SPINDLE_UNLOAD, 7, 12, uncreated_tool) != 0) {
return 58;
}
if (tooldata_db_notify(TOOL_OFFSET, 7, 12, uncreated_tool) != 0) {
return 59;
}
std::string missing_db_program_with_arg_string =
make_temp_path("cnc_tooldata_probe_missing_db_program arg");
std::vector<char> missing_db_program_with_arg(
@@ -147,6 +224,12 @@ int main() {
}
alignas(EMC_TOOL_STAT) unsigned char toolstat_storage[sizeof(EMC_TOOL_STAT)] = {};
auto *toolstat = reinterpret_cast<EMC_TOOL_STAT *>(toolstat_storage);
// LinuxCNC src/emc/tooldata/tooldata_nml.cc accepts a non-null table
// pointer before NML-backed tooldata operations can use it.
CANON_TOOL_TABLE nml_tool_table[CANON_POCKETS_MAX] = {};
if (tool_nml_register(nml_tool_table) != 0) {
return 77;
}
if (tool_mmap_creator(toolstat, 1) != 0) {
return 15;
}
@@ -300,6 +383,16 @@ int main() {
reloaded.offset.tran.z != 3.5 || reloaded.diameter != 8.0) {
return 14;
}
tooldata_last_index_set(12);
tooldata_reset();
// LinuxCNC src/emc/tooldata/tooldata_mmap.cc clears table entries in
// tooldata_reset() without changing the mmap header last_index.
if (tooldata_last_index_get() != 12) {
return 78;
}
if (tooldata_find_index_for_tool(7) != -1) {
return 79;
}
tooldata_init(false);
std::string nonrandom_path_template = make_temp_path("cnc_tooldata_nonrandom_probe_XXXXXX");
@@ -338,5 +431,21 @@ int main() {
return 39;
}
const pid_t recreate_child = fork();
if (recreate_child < 0) {
return 60;
}
if (recreate_child == 0) {
tool_mmap_creator(nullptr, 1);
_exit(61);
}
int recreate_status = 0;
if (waitpid(recreate_child, &recreate_status, 0) != recreate_child) {
return 62;
}
if (!WIFEXITED(recreate_status) || WEXITSTATUS(recreate_status) == 0) {
return 63;
}
return 0;
}

View File

@@ -48,6 +48,8 @@ int tooldata_db_init(char progname_plus_args[], int) {
}
int tooldata_db_notify(tool_notify_t, int, int, CANON_TOOL_TABLE) {
// LinuxCNC source basis: src/emc/tooldata/tooldata_db.cc returns 0 when
// db_live is false, before formatting or sending any DB notification.
return 0;
}

View File

@@ -42,6 +42,8 @@ advanced in this order:
Each step should stay source-backed and close one small gap at a time. A single
pass should advance one workstream context only; crossing into another context
requires naming the dependency and keeping the edit set minimal.
The synchronized source-map guard for these contexts is
`docs/linuxcnc-workstreams-source-map.md`.
## Dialect expansion

View File

@@ -0,0 +1,60 @@
# LinuxCNC browser app source map
This map records the source basis and boundaries for the browser application
entry point. It is an app/bridge guard only; it must not add CNC behavior and
must not expand the temporary smoke parser.
## LinuxCNC Source Basis
- `src/emc/rs274ngc/interp_internal.hh` defines
`RS274NGC_PARAMETER_FILE_NAME_DEFAULT` as `rs274ngc.var`.
- `src/emc/rs274ngc/interp_internal.hh` defines
`RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX` as `.bak`.
- `src/emc/rs274ngc/rs274ngc_pre.cc` restores parameter files through
`Interp::restore_parameters()`.
- `src/emc/rs274ngc/rs274ngc_pre.cc` saves parameter files through
`Interp::save_parameters()`, including `filename + ".new"` and
`filename + ".bak"` behavior.
- `src/emc/rs274ngc/rs274ngc_pre.cc` orders parameter saves as backup unlink,
backup link, then temporary-file rename.
- `src/emc/rs274ngc/rs274ngc_pre.cc` reads program blocks through `read_text()`
after the browser bridge mirrors the OPFS program into the WASM filesystem.
## App Coverage
| Entry | Boundary |
| --- | --- |
| `web/index.html` | Loads the browser app module and exposes only the LinuxCNC backend selector. |
| `web/src/app.js` | Creates a required OPFS-backed simulator workspace and parses browser programs with a LinuxCNC parameter file. |
| `web/test-browser-wasm-smoke-app-sections.js` | Verifies app-level OPFS program save, parameter save, parameter backup, and reload behavior. |
## Boundaries
- The app must use the existing `createWasmSimulator()` OPFS API; it must not
add browser filesystem persistence outside OPFS.
- The app must persist the current program at `programs/current.ngc`.
- The app must persist LinuxCNC parameter state at `parameters/rs274ngc.var`.
- The app must require OPFS for browser program parsing and call
`parseFileWithParameterFile(programPath, parameterPath, "linuxcnc", ...)`.
- The visible backend selector must stay limited to the LinuxCNC RS274 backend.
- The app must not read the backend selector as a dynamic backend source; it
must force `linuxCncBackend` for browser parsing.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-browser-app-source-map.sh` before consuming app-backed
browser artifacts or source-linked LinuxCNC objects.
- The browser app source-map guard validates LinuxCNC source-backed bridge
routing and OPFS policy only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-browser-app-source-map.sh
./test-native.sh
./test-linuxcnc-source-link.sh
./build-wasm.sh
```
`check-linuxcnc-browser-app-source-map.sh` keeps this document, LinuxCNC source
anchors, app OPFS paths, app parse routing, and app smoke coverage synchronized.

View File

@@ -0,0 +1,69 @@
# LinuxCNC build source map
This map records the source basis and boundaries for native, source-link, and
WASM build entry points. It is a build-policy guard only; it must not add CNC
behavior and must not expand the temporary smoke parser.
## LinuxCNC Source Basis
- `src/emc/rs274ngc/Submakefile` defines `LIBRS274SRCS`, including
`rs274ngc_pre.cc`, and links RS274 against LinuxCNC INI, HAL, Python plugin,
and tooldata libraries.
- `src/emc/tooldata/Submakefile` defines the LinuxCNC tooldata common, mmap,
NML, database, reader, and watcher sources tracked by native and WASM
manifests.
- `src/emc/ini/Submakefile` defines `liblinuxcncini` and exported INI headers
used by source-linked and WASM-safe probes.
- `src/emc/pythonplugin/Submakefile` defines `libpyplugin`, which remains a
source-tracked Python/remap dependency for source-link and WASM blocker
coverage.
- `src/emc/kinematics/Submakefile` and `src/emc/kinematics/kinematics.h`
define the kinematics source/header surface consumed by switchkins and RTCP
build coverage.
## Build Coverage
| Entry | Boundary |
| --- | --- |
| `test-native.sh` | Runs source-map guards, cached manifest checks, native bridge/API/RS274 probes, and Node/browser smoke after LinuxCNC source-backed native coverage. |
| `test-linuxcnc-source-link.sh` | Compiles manifest-selected LinuxCNC RS274 objects directly with source-link support objects and project dump bridges. |
| `test-linuxcnc-source-syntax.sh` | Keeps native source syntax validation on persistent dependency-tracked stamps. |
| `test-linuxcnc-wasm-cmake-safe-probe.sh` | Configures source-backed WASM-safe probe targets with persistent CMake signatures. |
| `build-wasm.sh` | Runs source-map guards, blocker, syntax, object, CMake, tooldata, interp, Python, `rs274ngc_pre`, and runtime probes before copying browser artifacts. |
## Boundaries
- Default WASM manifest partition must remain 26 wasm-safe `core` sources and
9 `blocked` sources.
- Build paths must keep `CNC_SIM_BUILD_JOBS` as scheduling only. Persistent
object/configure signatures must not include it.
- Hot object probes must use explicit `BUILD_RULE_VERSION` or
`CONFIGURE_RULE_VERSION` fields instead of hashing whole scripts.
- `build-wasm.sh` must configure with `CNC_SIM_ENABLE_SMOKE_BACKEND=OFF` and
`CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON`.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run the same source-map guards before consuming generated config aliases,
source-linked LinuxCNC objects, or browser artifacts.
- `build-wasm.sh` must build `cnc_sim_wasm_runtime_probe` before
`cnc_sim_wasm`, copy `cnc_sim.js` and `cnc_sim.wasm` exactly once, compare
copied artifacts with `cmp -s`, and run Node smoke before browser smoke.
- Build directory overrides must be non-empty, whitespace-free, and outside the
filesystem root.
- The build source-map guard validates LinuxCNC build-source ownership,
manifest partitions, and artifact ordering only; it does not interpret
G-code.
## Checks
Run:
```bash
./check-linuxcnc-build-source-map.sh
./test-native.sh
./test-linuxcnc-source-link.sh
./build-wasm.sh
```
`check-linuxcnc-build-source-map.sh` keeps this document, LinuxCNC build-source
anchors, manifest partition assumptions, build script ordering, and
source-backed build-policy boundaries synchronized.

View File

@@ -0,0 +1,69 @@
# LinuxCNC kinematics source map
This map tracks the LinuxCNC source/configuration basis for kinematics,
switchkins, RTCP, and M428/M429/M430 remap coverage. It is intentionally a
source-coverage document: the manifest entries here must not become browser or
application behavior by themselves. It must not add CNC behavior and must not
expand the temporary smoke parser.
## Manifest groups
The kinematics manifest currently contains these LinuxCNC-root relative source
groups:
| Group | Count | Source coverage role |
| --- | ---: | --- |
| `core` | 30 | LinuxCNC kinematics and posemath C/C++ sources. |
| `config` | 33 | LinuxCNC switchkins INI/HAL/POSTGUI sources used by M428/M429/M430 and RTCP coverage. |
| `remap` | 31 | LinuxCNC M428/M429/M430 remap subroutines. |
| `component` | 8 | LinuxCNC halcompile component sources referenced by kinematics configs. |
| `generated` | 8 | LinuxCNC halcompile-generated C sources adjacent to component coverage. |
| `header` | 13 | LinuxCNC kinematics and posemath declarations. |
| `metadata` | 34 | LinuxCNC build/manpage metadata adjacent to covered kinematics sources. |
| `asset` | 122 | LinuxCNC INI-adjacent HAL, README, GUI, demo, PYVCP, and other source assets. |
| `tooldata` | 10 | LinuxCNC tool table files referenced by covered M428/M429/M430 INI files. |
## LinuxCNC Source Anchors
- `src/emc/kinematics/Submakefile` defines the kinematics build source surface,
including `GENSERKINSSRCS`.
- `src/emc/kinematics/kinematics.h` declares `kinematicsInverse()` and
`kinematicsSwitch()` for switchkins-capable modules.
- `configs/sim/axis/vismach/5axis/bridgemill/5axis.ini` wires
`motion.switchkins-type` and representative M428/M429/M430 switchkins
configuration.
- `configs/sim/axis/vismach/5axis/bridgemill/remap_subs/428remap.ngc`
provides a representative `#<kinstype>` remap assignment.
## Boundaries
- `asset`, `metadata`, `header`, and `tooldata` entries are source coverage,
not browser API surface.
- Browser-side file persistence remains OPFS-only and is not expanded by this
manifest.
- Switchkins/remap generation must continue to derive M428/M429/M430 cases from
LinuxCNC INI/HAL/remap sources, not from hand-authored behavior.
- RTCP and five-axis coverage must remain tied to LinuxCNC kinematics sources,
remap files, and configuration files.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-kinematics-source-map.sh` before consuming generated
config aliases, source-linked LinuxCNC objects, or browser artifacts.
- The kinematics source-map guard validates LinuxCNC source/config ownership
and manifest drift only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-kinematics-source-map.sh
./check-linuxcnc-inputs.sh linuxcnc-kinematics-source-files.txt --require-kinematics-complete
./check-linuxcnc-switchkins-remap-table.sh linuxcnc-kinematics-source-files.txt
./test-native.sh
./test-linuxcnc-source-link.sh
```
`check-linuxcnc-kinematics-source-map.sh` keeps this document's group counts in
sync with `linuxcnc-kinematics-source-files.txt`; the input and table checkers
validate LinuxCNC source existence, directory completeness, remap kinstype
notes, and switchkins/remap table generation.

View File

@@ -0,0 +1,74 @@
# LinuxCNC OPFS source map
This map records the source basis for browser-side LinuxCNC parameter-file
persistence. It is a policy and bridge map only; it must not add independent filesystem behavior or expand the temporary smoke parser.
## LinuxCNC Source Basis
- `src/emc/rs274ngc/interp_internal.hh` defines
`RS274NGC_PARAMETER_FILE_NAME_DEFAULT` as `rs274ngc.var`.
- `src/emc/rs274ngc/interp_internal.hh` defines
`RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX` as `.bak`.
- `src/emc/rs274ngc/rs274ngc_pre.cc` implements
`Interp::restore_parameters()` for parameter-file reads.
- `src/emc/rs274ngc/rs274ngc_pre.cc` implements `Interp::open()` by opening
the NC program file with `fopen(filename, "r")`, then `Interp::_read()`
feeds `_setup.file_pointer` into `read_text()`.
- `src/emc/rs274ngc/rs274ngc_pre.cc` implements
`Interp::save_parameters()` for `filename + ".new"`, backup, and replacement
writes.
- `src/emc/rs274ngc/rs274ngc_pre.cc` unlinks `filename + ".bak"`, links the
previous parameter file to that backup path, then renames `filename + ".new"`
over the configured parameter file.
## OPFS Coverage
| Entry | Boundary |
| --- | --- |
| `web/src/wasm-core.js` | Keeps LinuxCNC program reads and parameter restore/save bridge files under the OPFS workspace and cleans WASM scratch files. |
| `web/src/wasm-core.d.ts` | Exposes only the OPFS-backed workspace, directory, parameter-file, and mount/workspace option types used by the bridge. |
| `web/src/app.js` | Persists browser programs at `programs/current.ngc` and LinuxCNC parameters at `parameters/rs274ngc.var`. |
| `web/test-browser-wasm-smoke-opfs-basic-sections.js` | Verifies OPFS program file reads through LinuxCNC `read_text()` and rejects unsafe paths. |
| `web/test-browser-wasm-smoke-opfs-parameter-sections.js` | Verifies OPFS parameter restore, save, `.new`, `.bak`, failure, and stale-state semantics. |
| `web/test-browser-wasm-smoke-opfs-workspace-sections.js` | Aggregates OPFS basic, parameter, mirror, and directory checks under browser OPFS availability. |
| `web/test-browser-wasm-smoke-opfs-mirror-sections.js` | Verifies OPFS/WASM mirror persistence used by source-backed file parsing and parameter movement. |
| `web/test-browser-wasm-smoke-opfs-directory-sections.js` | Verifies OPFS directory persistence and WASM mirror refresh without polluting mirrors during metadata queries. |
| `web/test-browser-wasm-smoke-opfs-policy-sections.js` | Verifies OPFS workspace and mount policy, including the browser rule that OPFS cannot be disabled. |
| `web/test-browser-wasm-smoke-app-sections.js` | Verifies app-level OPFS program, parameter, backup, and reload behavior. |
## Browser Bridge Boundary
- `web/src/wasm-core.js` must keep LinuxCNC parameter persistence routed
through an OPFS workspace.
- Browser contexts with OPFS support must not disable OPFS.
- OPFS workspace paths must remain relative paths inside the CNC workspace, and
OPFS mount points must remain absolute paths inside the Emscripten
filesystem.
- Browser app and wasm bridge code must not use Local Storage, IndexedDB,
File Picker, Directory Picker, WebKit filesystem, or FileReader persistence
paths for CNC programs, LinuxCNC parameter files, or mirrored workspace files.
- Browser OPFS smoke harness files must also stay free of Local Storage,
IndexedDB, File Picker, Directory Picker, WebKit filesystem, and FileReader
persistence paths; they may only verify OPFS-backed browser persistence.
- `web/src/app.js` must parse browser programs through
`parseFileWithParameterFile()` so the program file and LinuxCNC parameter
state both live under OPFS.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-opfs-source-map.sh` before consuming app-backed browser
artifacts or source-linked LinuxCNC objects.
- The OPFS source-map guard validates LinuxCNC source-backed filesystem bridge
policy only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-opfs-source-map.sh
./test-native.sh
./test-linuxcnc-source-link.sh
./build-wasm.sh
```
`check-linuxcnc-opfs-source-map.sh` keeps this document, the LinuxCNC source
anchors, and the browser bridge guardrails synchronized.

View File

@@ -209,7 +209,7 @@ This matrix tracks LinuxCNC feature coverage for the web/WASM simulator. A featu
| Modal state save/restore `M70`-`M73` | covered for main-program save/invalidate/restore plus O-word subroutine autorestore in smoke parser, with native/source regressions checking restored distance mode, units, and XY plane motion | `tests/gcode/linuxcnc_modal_state.ngc`, `tests/gcode/linuxcnc_modal_autorestore.ngc` |
| LinuxCNC read-only named parameters | common modal/unit/feed/spindle reads and additional ccomp/spindle-mode readonly reads are covered through LinuxCNC native/source backends | `tests/gcode/linuxcnc_readonly_named_parameters.ngc`, `tests/gcode/linuxcnc_readonly_named_parameters_extra.ngc`, `tests/gcode/linuxcnc_lathe_diameter_mode.ngc` |
| LinuxCNC numbered parameters | native/source coverage checks readonly numbered-parameter rejection plus probe, tool, G28/G30, G52/G92, G5X/G59.3, debug/M66 result, tool-offset, current-position, `restore_parameters()`/`save_parameters()` persistence, and volatile global numbered parameters from LinuxCNC source state | `tests/gcode/linuxcnc_numbered_parameters.ngc`, `tests/gcode/linuxcnc_rotary_probe_numbered_parameters.ngc`, `tests/gcode/linuxcnc_multi_rotary_probe_numbered_parameters.ngc`, `tests/gcode/linuxcnc_tool_info_zero_parameter.ngc`, `tests/gcode/linuxcnc_spindle_tool_info_parameter.ngc`, `tests/gcode/linuxcnc_readonly_numbered_parameter_assign_error.ngc`, `tests/gcode/linuxcnc_g28_g30_numbered_parameters.ngc`, `tests/gcode/linuxcnc_rotary_g28_g30_numbered_parameters.ngc`, `tests/gcode/linuxcnc_g92_numbered_parameters.ngc`, `tests/gcode/linuxcnc_rotary_g92_numbered_parameters.ngc`, `tests/gcode/linuxcnc_g52_g92_numbered_interaction.ngc`, `tests/gcode/linuxcnc_g5x_numbered_parameters.ngc`, `tests/gcode/linuxcnc_g59_3_numbered_parameters.ngc`, `tests/gcode/linuxcnc_g59_3_rotary_numbered_parameters.ngc`, `tests/gcode/linuxcnc_g5x_rotary_numbered_parameters.ngc`, `tests/gcode/linuxcnc_g54_rotary_numbered_parameters.ngc`, `tests/gcode/linuxcnc_debug_level_parameter.ngc`, `tests/gcode/linuxcnc_m66_result_parameter.ngc`, `tests/gcode/linuxcnc_tool_offset_numbered_parameters.ngc`, `tests/gcode/linuxcnc_rotary_tool_offset_numbered_parameters.ngc`, `tests/gcode/linuxcnc_current_position_numbered_parameters.ngc`, `tests/gcode/linuxcnc_persistent_parameters_set.ngc`, `tests/gcode/linuxcnc_persistent_parameters_check.ngc`, `tests/gcode/linuxcnc_rotary_persistent_parameters_set.ngc`, `tests/gcode/linuxcnc_rotary_persistent_parameters_check.ngc`, `tests/gcode/linuxcnc_g54_persistent_parameters_set.ngc`, `tests/gcode/linuxcnc_g54_persistent_parameters_check.ngc`, `tests/gcode/linuxcnc_user_persistent_parameter_set.ngc`, `tests/gcode/linuxcnc_user_persistent_parameter_check.ngc`, `tests/gcode/linuxcnc_user_persistent_parameter_existing_check.ngc`, `tests/gcode/linuxcnc_user_persistent_parameter_boundary_set.ngc`, `tests/gcode/linuxcnc_user_persistent_parameter_boundary_check.ngc`, `tests/gcode/linuxcnc_volatile_global_parameter_set.ngc`, `tests/gcode/linuxcnc_volatile_global_parameter_check.ngc` |
| Feed and motion control modes `G93/G94/G95`, `G61/G61.1/G64` | covered through LinuxCNC native/source backends | `tests/gcode/linuxcnc_feed_modes.ngc`, `tests/gcode/linuxcnc_motion_modes.ngc` |
| Feed and motion control modes `G93/G94/G95`, `G61/G61.1/G64` | covered through LinuxCNC native/source backends, including `G64 P` path tolerance and `G64 Q` naivecam tolerance from `convert_control_mode()` | `tests/gcode/linuxcnc_feed_modes.ngc`, `tests/gcode/linuxcnc_motion_modes.ngc`, `tests/gcode/linuxcnc_control_mode_tolerances.ngc` |
| Dwell `G4 P...` | covered through LinuxCNC native/source backends for missing-P rejection and boundary values around LinuxCNC's exact `P-1.0` missing-value sentinel; neighboring negative P values are passed through to `DWELL` by `convert_dwell()` | `tests/gcode/linuxcnc_g4_missing_p_error.ngc`, `tests/gcode/linuxcnc_g4_negative_p_error.ngc`, `tests/gcode/linuxcnc_g4_dwell_boundaries.ngc` |
| Canned cycle `G73`-`G89`/`G80` | covered for drilling expand-to-canon path, `G18/G19` plane-specific depth-axis mapping for representative drilling/boring cycles, `G17.1` extended-plane depth/retract mapping, and native/source LinuxCNC `interp_cycles.cc` validation of repeat counts, required R/depth words, invalid rotary axes including follow-up modal-cycle blocks, Q/P words, and spindle state | `tests/gcode/linuxcnc_canned_cycle.ngc`, `tests/gcode/linuxcnc_canned_cycles_extended.ngc`, `tests/gcode/linuxcnc_canned_cycle_planes.ngc`, `tests/gcode/linuxcnc_extended_plane_cycle.ngc`, `tests/gcode/linuxcnc_canned_cycle_l0_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_missing_r_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_missing_z_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g18_missing_y_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g19_missing_x_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_r_less_z_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g18_r_less_y_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g19_r_less_x_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_u_in_xy_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_a_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_b_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_c_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_reject_rotary_followup.ngc`, `tests/gcode/linuxcnc_canned_cycle_g73_missing_q_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g83_missing_q_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g73_q0_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g83_q0_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g82_missing_p_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g86_missing_p_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g88_missing_p_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g89_missing_p_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g74_clockwise_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g84_counterclockwise_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g74_stopped_spindle_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g84_stopped_spindle_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g86_stopped_spindle_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g88_stopped_spindle_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g87_stopped_spindle_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g87_missing_k_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g87_missing_i_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g87_missing_j_error.ngc` |
| Coordinate offset Canon events `G10 L2`, `G10 L20`, `G10 P0 active-system targeting`, `G54`-`G59.3` selection, `G52`, `G92/G92.1/G92.2/G92.3` | covered through LinuxCNC native/source backends, including all nine G5X indices | `tests/gcode/linuxcnc_coordinate_offsets.ngc`, `tests/gcode/linuxcnc_coordinate_l20.ngc`, `tests/gcode/linuxcnc_coordinate_p0.ngc`, `tests/gcode/linuxcnc_coordinate_select_all.ngc`, `tests/gcode/linuxcnc_g92_restore.ngc`, `tests/gcode/linuxcnc_g52_offset.ngc` |

View File

@@ -1,6 +1,8 @@
# LinuxCNC rs274 source map
This is the working map for moving from native `librs274` linking to source-level compilation and then to wasm.
This is the working map for moving from native `librs274` linking to
source-level compilation and then to wasm. It is a source-manifest guard only;
it must not add CNC behavior and must not expand the temporary smoke parser.
## Compile assumptions
@@ -108,6 +110,66 @@ dependencies are replaced:
- `src/emc/rs274ngc/pyinterp1.cc`
- `src/emc/rs274ngc/pyparamclass.cc`
### WASM-tracked headers
These LinuxCNC headers are tracked by the wasm manifest because the
browser-safe source-link probes compile against them or shim their declarations:
- `src/emc/rs274ngc/array1.hh`
- `src/emc/rs274ngc/interp_array_types.hh`
- `src/emc/rs274ngc/interp_base.hh`
- `src/emc/rs274ngc/interp_fwd.hh`
- `src/emc/rs274ngc/interp_inspection.hh`
- `src/emc/rs274ngc/interp_internal.hh`
- `src/emc/rs274ngc/interp_parameter_def.hh`
- `src/emc/rs274ngc/interp_python.hh`
- `src/emc/rs274ngc/interp_queue.hh`
- `src/emc/rs274ngc/modal_state.hh`
- `src/emc/rs274ngc/paramclass.hh`
- `src/emc/rs274ngc/rs274ngc.hh`
- `src/emc/rs274ngc/rs274ngc_interp.hh`
- `src/emc/rs274ngc/rs274ngc_return.hh`
- `src/emc/rs274ngc/units.h`
- `src/emc/pythonplugin/python_plugin.hh`
- `src/emc/ini/inifile.h`
- `src/emc/ini/inifile.hh`
- `src/emc/nml_intf/emc.hh`
- `src/emc/nml_intf/emc_nml.hh`
- `include/hal.h`
- `src/libnml/nml/nmlmsg.hh`
- `src/libnml/nml/stat_msg.hh`
- `src/libnml/rcs/rcs_print.hh`
- `src/rtapi/rtapi.h`
- `src/rtapi/rtapi_string.h`
### WASM-blocked headers
These headers are tracked for source coverage but should not become browser API
surface until their Boost.Python dependency is replaced:
- `src/emc/rs274ngc/boost_pyenum_macros.hh`
### WASM metadata sources
These LinuxCNC source and build metadata files document the source basis for
the browser-safe shims and manifest completeness checks:
- `src/emc/rs274ngc/Submakefile`
- `src/emc/rs274ngc/meson.build`
- `src/emc/pythonplugin/python_plugin.cc`
- `src/emc/task/emctaskmain.cc`
- `src/emc/task/taskclass.cc`
- `src/emc/task/emccanon.cc`
- `src/emc/sai/dummyemcstat.cc`
- `src/emc/ini/Submakefile`
- `src/libnml/nml/stat_msg.cc`
- `src/libnml/nml/nmlmsg.cc`
- `src/libnml/rcs/rcs_print.cc`
- `src/rtapi/uspace_common.h`
- `src/hal/hal_lib.c`
- `src/emc/tooldata/tooldata_db.cc`
- `src/emc/tooldata/tooldata_nml.cc`
### Browser-hostile dependencies to replace
- Python/Boost.Python remap and named parameter hooks.
@@ -116,6 +178,33 @@ dependencies are replaced:
- persistent parameter file writes.
- dynamic INI/HAL queries.
## Boundaries
- Native and WASM source lists in this document must match
`linuxcnc-rs274-source-files.txt` and
`linuxcnc-rs274-wasm-source-files.txt` exactly.
- Python binding modules, WASM-blocked sources, and WASM-blocked headers are
source coverage only.
- They must not become browser API fields or temporary smoke parser fallback behavior.
- Browser-hostile dependencies must remain tracked blockers until they are
ported, trimmed, wrapped, or replaced by source-backed browser-safe LinuxCNC
adaptations.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-rs274-source-map.sh` before consuming generated native
objects, source-linked LinuxCNC objects, or browser artifacts.
- The RS274 source-map guard validates LinuxCNC source ownership and manifest
drift only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-rs274-source-map.sh
./test-native.sh
./test-linuxcnc-source-link.sh
```
The wasm-safe runtime now constructs its detached `EMC_STAT` through LinuxCNC
`src/emc/nml_intf/emcops.cc`. The singleton follows LinuxCNC
`src/emc/sai/dummyemcstat.cc`, and the required `NMLmsg`/`RCS_STAT_MSG`

View File

@@ -63,6 +63,9 @@ Efficiency rules:
- Each pass should advance one reasonable, reviewable context. Do not use a
nearby failing smoke assertion, broad fixture, or convenient touched file as
permission to expand the pass into another workstream.
- Keep the six active workstream contexts synchronized with
`docs/linuxcnc-workstreams-source-map.md` so source-map guards, architecture,
and this policy stay aligned.
- Do not add a smoke-only behavior path when the LinuxCNC-backed route already
exists; add coverage to the source-backed route instead.
- The legacy explicit smoke backend must not be enabled together with the

View File

@@ -0,0 +1,54 @@
# LinuxCNC switchkins/remap source map
This map records the source basis for generated M428/M429/M430 switchkins
aliases and config cases. It is a generator/table map only; it must not add CNC behavior, must not expand the temporary smoke parser, and must not expose LinuxCNC source-only assets as browser API fields.
## LinuxCNC Source Basis
- `configs/sim/**.ini` files with `REMAP = M428`, `REMAP = M429`, or
`REMAP = M430` provide the switchkins configuration roots.
- `remap_subs/{428,429,430}remap.ngc` files provide the adjacent
`#<kinstype>` assignments.
- `src/hal/utils/halcmd_commands.cc` `do_loadusr_cmd()` and
`getopt("+wWin:")` define the `loadusr` option model used by the generator.
- `src/hal/utils/halcmd_completion.c` documents common `loadusr`
`-W/-Wn/-w/-iw` forms preserved by the generator.
## Generated Coverage
| Output | Count | Source coverage role |
| --- | ---: | --- |
| `core/src/linuxcnc_switchkins_remap_table.inc` | 177 | Generated switchkins aliases from LinuxCNC INI/HAL/remap sources. |
| `core/tests/linuxcnc_switchkins_remap_config_cases.inc` | 616 | Generated C++ config cases for accepted switchkins config fields. |
| `web/public/linuxcnc_switchkins_remap_config_cases.json` | 616 | Generated browser-readable config cases mirroring the C++ cases. |
| LinuxCNC M428/M429/M430 remap sources | 31 | LinuxCNC `#<kinstype>` source assignments. |
| LinuxCNC switchkins REMAP INI sources | 16 | LinuxCNC configs that declare M428/M429/M430 remaps. |
## Boundaries
- `[EMCIO] TOOL_TABLE` entries are source coverage only. They must stay in the kinematics manifest as `tooldata` entries and must not become generated switchkins aliases, config fields, or browser option keys.
- `asset`, `metadata`, and `tooldata` inputs referenced by switchkins configs
are source coverage, not browser API surface.
- Generated tables must continue to derive M428/M429/M430 kinstypes from
LinuxCNC remap `#<kinstype>` assignments, not from hand-authored behavior.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-switchkins-remap-source-map.sh` before consuming
generated config aliases, source-linked LinuxCNC objects, or browser
artifacts.
- The switchkins/remap source-map guard validates LinuxCNC source/config
ownership and generated table drift only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-switchkins-remap-source-map.sh
./check-linuxcnc-switchkins-remap-table.sh linuxcnc-kinematics-source-files.txt
./test-native.sh
./test-linuxcnc-source-link.sh
```
`check-linuxcnc-switchkins-remap-source-map.sh` keeps this document, generator
source anchors, generated output counts, and source-only `TOOL_TABLE` boundary
synchronized.

View File

@@ -0,0 +1,99 @@
# LinuxCNC tooldata source map
This map records the source basis and boundaries for LinuxCNC tooldata coverage
in native, source-link, switchkins, and WASM-safe build paths. It is a
source-link/shim guard only; it must not add CNC behavior and must not expand the temporary smoke parser.
## LinuxCNC Source Basis
- `src/emc/tooldata/Submakefile` selects `tooldata_mmap.cc` or
`tooldata_nml.cc`, and always includes `tooldata_common.cc`,
`tooldata_db.cc`, and `tool_watch.cc`.
- `src/emc/tooldata/tooldata_common.cc` provides tool table parsing,
formatting, load/save, and database-mode entry points.
- `src/emc/tooldata/tooldata_mmap.cc` provides native mmap tool table storage.
- `src/emc/tooldata/tooldata_nml.cc` provides the NML tool table registration
backend selected by `TOOL_NML_FLAG`.
- `src/emc/tooldata/tooldata_db.cc` provides external database entry points.
- `src/emc/tooldata/tooldata_db.cc` returns `-1` from
`tooldata_db_getall()` when the external DB is not live, and returns `0`
from `tooldata_db_notify()` without side effects in that same state.
- `src/emc/tooldata/tool_mmap_read.cc` is the mmap tool table reader utility
source from the same LinuxCNC tooldata build metadata; it reads through
`tool_mmap_user()`, `tooldata_last_index_get()`, and `tooldata_get()`.
- `src/emc/tooldata/tool_watch.cc` is the NML/mmap tool table watcher utility
source from the same LinuxCNC tooldata build metadata; it reads
`EMC_STAT::io.tool` fields through `RCS_STAT_CHANNEL`.
- `src/emc/tooldata/tooldata.hh` declares the tooldata API used by RS274 and
WASM-safe shims.
## Manifest And Shim Coverage
| Coverage | Boundary |
| --- | --- |
| `linuxcnc-rs274-source-files.txt` | Tracks the full LinuxCNC `src/emc/tooldata` source directory and `tooldata.hh`. |
| `linuxcnc-rs274-wasm-source-files.txt` | Links `tooldata_common.cc`, blocks native `tooldata_mmap.cc`, and records runtime shim source bases for `tooldata_db.cc` and `tooldata_nml.cc`. |
| `linuxcnc-kinematics-source-files.txt` | Tracks 10 LinuxCNC `TOOL_TABLE` files referenced by covered M428/M429/M430 INI sources as `tooldata` source coverage only. |
| `core/wasm_shims/tooldata/tooldata_mmap_backend.cc` | Adapts LinuxCNC `tooldata_mmap.cc` exports to WASM-safe in-memory storage. |
| `core/wasm_shims/tooldata/tooldata_runtime_stubs.cc` | Adapts LinuxCNC `tooldata_nml.cc` and `tooldata_db.cc` runtime entry points without native services. |
| `test-linuxcnc-wasm-tooldata-link.sh` | Links the wasm-safe tooldata shim against LinuxCNC tooldata runtime expectations. |
| `test-linuxcnc-wasm-tooldata-common-link.sh` | Links LinuxCNC `tooldata_common.cc` through the wasm-safe standalone probe. |
| `test-linuxcnc-wasm-tooldata-mmap-symbols.sh` | Compares wasm mmap backend exports with LinuxCNC `tooldata_mmap.cc`. |
| `test-linuxcnc-wasm-tooldata-runtime-symbols.sh` | Compares runtime stub exports with LinuxCNC `tooldata_db.cc` and `tooldata_nml.cc`. |
## External DB Stub Boundaries
- `core/wasm_shims/tooldata/tooldata_runtime_stubs.cc` must keep external DB services unavailable for browser-safe probes; it must not fork, exec, poll,
or keep a live external database process.
- `tooldata_db_init()` must preserve the LinuxCNC `MAX_DB_PROGRAM_ARGS=10`, `strtok_r()` argument splitting, and `access(..., X_OK)` rejection edges from
`src/emc/tooldata/tooldata_db.cc` before returning `-1`.
- `tooldata_db_getall()` must return `-1` while `db_live` is false, and
`tooldata_db_notify()` must return `0` without side effects while `db_live`
is false.
## Mmap Backend Boundaries
- `core/wasm_shims/tooldata/tooldata_mmap_backend.cc` must preserve the
LinuxCNC `tooldata_mmap.cc` export surface for the mmap creator/user/close lifecycle while using an in-memory replacement for native mmap.
- The mmap backend must keep the LinuxCNC random-toolchanger flag and
`last_index` initialization edges from `tool_mmap_creator()`, and
`tooldata_last_index_get()` must preserve the last-index unavailable return.
- The mmap backend must keep LinuxCNC's single-creator process boundary from
the `static inited` guard in `tool_mmap_creator()`, including after
`tool_mmap_close()` clears browser-safe in-memory state.
- `tool_mmap_user()` must keep LinuxCNC's unavailable mmap failure boundary,
and `tool_mmap_close()` must clear the browser-safe in-memory state without
adding filesystem mmap behavior.
## Boundaries
- Tool table files referenced by switchkins INI files are source coverage only;
they must not become browser API fields or generated switchkins aliases.
- WASM-safe tooldata shims must preserve the LinuxCNC export surface checked by
symbol probes.
- WASM-safe tooldata runtime stubs must keep external database services
unavailable while preserving LinuxCNC inactive-DB return values.
- Native and source-link paths must keep the full LinuxCNC `src/emc/tooldata`
source directory tracked in the native RS274 manifest.
- `build-wasm.sh` must run tooldata link and symbol probes before copying
browser artifacts.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-tooldata-source-map.sh` before consuming tooldata-backed
native objects, source-linked LinuxCNC objects, or browser artifacts.
- The tooldata source-map guard validates LinuxCNC source/shim ownership and
manifest drift only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-tooldata-source-map.sh
./test-native.sh
./test-linuxcnc-source-link.sh
./build-wasm.sh
```
`check-linuxcnc-tooldata-source-map.sh` keeps this document, LinuxCNC tooldata
source anchors, manifest coverage, WASM shim/probe boundaries, and switchkins
TOOL_TABLE source-only policy synchronized.

View File

@@ -0,0 +1,98 @@
# LinuxCNC WASM blockers source map
This map records the source basis for the WASM blocker categories used by
`analyze-linuxcnc-wasm-blockers.sh` and `test-linuxcnc-wasm-blockers.sh`. It is
a source-link/build guard only; it does not add CNC behavior and must not become smoke parser behavior.
## LinuxCNC Source Basis
- `src/emc/rs274ngc/gcodemodule.cc`, `canonmodule.cc`, `interpmodule.cc`, and
`py*.cc` provide the Python and Boost.Python module binding blockers.
- `src/emc/rs274ngc/interp_python.cc`, `interp_remap.cc`,
`interp_namedparams.cc`, `interp_o_word.cc`, `interp_setup.cc`, and
`rs274ngc_pre.cc` keep Python/Boost.Python remap paths as shimmed core
sources.
- `src/emc/rs274ngc/interp_base.cc` is the core source that still carries
`dlopen` use satisfied by the wasm dlfcn shim.
- `src/emc/tooldata/tooldata_mmap.cc` is the native mmap backend blocker
replaced by the wasm-safe tooldata mmap backend shim.
- `src/emc/rs274ngc/interp_o_word.cc` uses native directory traversal for
O-word subroutine lookup; browser persistence remains OPFS-only.
- `src/emc/rs274ngc/rs274ngc_pre.cc` implements LinuxCNC parameter-file replacement through `filename + ".new"`, `filename + ".bak"`, `unlink`, `link`, and `rename`; browser persistence remains OPFS-only.
## Manifest Coverage
| Manifest group | Count | Role |
| --- | ---: | --- |
| `core` | 26 | LinuxCNC RS274/tooldata/INI/NML sources compiled or shim-satisfied by WASM probes. |
| `blocked` | 9 | LinuxCNC Python binding and native tooldata sources tracked but replaced for WASM. |
| `header` | 26 | LinuxCNC headers needed by the WASM-safe source probes and shims. |
| `blocked-header` | 1 | Boost.Python helper header tracked as blocked browser API surface. |
| `metadata` | 15 | LinuxCNC build/source-basis files for shim coverage. |
## Analyzer Coverage
| Analyzer section | Count | Boundary |
| --- | ---: | --- |
| `python` | 8 | Blocked Python/Boost.Python module sources. |
| `dlopen` | 0 | blocked `dlopen` and blocked tooldata-users remain empty. |
| `tooldata` | 1 | Native tooldata mmap blocker. |
| `tooldata-users` | 0 | Blocked tooldata user sources remain empty. |
| `native-backend` | 1 | Native backend source excluded from browser linking. |
| `native-fs` | 1 | Native filesystem blocker excluded from browser linking. |
| `blocked-replacements` | 9 | Blocked LinuxCNC sources mapped to wasm-safe shim files. |
| `core-python-shimmed` | 6 | Core LinuxCNC Python/remap users compiled with Python/Boost.Python shims. |
| `core-dlopen-shimmed` | 1 | Core LinuxCNC `dlopen` user compiled with dlfcn shim. |
| `core-tooldata-shimmed` | 1 | Core LinuxCNC tooldata source compiled with wasm tooldata shims. |
| `core-tooldata-users-shimmed` | 2 | Core RS274/NML users of tooldata declarations. |
| `core-native-fs-shimmed` | 16 | Core sources with native filesystem includes or calls covered by WASM shims, including O-word lookup and parameter-file replacement. |
Required replacement set:
- `src/emc/rs274ngc/canonmodule.cc -> core/wasm_shims/linuxcnc_runtime_shim.cc`
- `src/emc/rs274ngc/gcodemodule.cc -> core/wasm_shims/linuxcnc_runtime_shim.cc`
- `src/emc/rs274ngc/interpmodule.cc -> core/wasm_shims/linuxcnc_runtime_shim.cc`
- `src/emc/rs274ngc/pyarrays.cc -> core/wasm_shims/python_c_api_shim.cc`
- `src/emc/rs274ngc/pyblock.cc -> core/wasm_shims/python_c_api_shim.cc`
- `src/emc/rs274ngc/pyemctypes.cc -> core/wasm_shims/python_c_api_shim.cc`
- `src/emc/rs274ngc/pyinterp1.cc -> core/wasm_shims/python_c_api_shim.cc`
- `src/emc/rs274ngc/pyparamclass.cc -> core/wasm_shims/python_c_api_shim.cc`
- `src/emc/tooldata/tooldata_mmap.cc -> core/wasm_shims/tooldata/tooldata_mmap_backend.cc`
Required tracked core examples:
- `src/emc/rs274ngc/interp_base.cc -> core/wasm_shims/dlfcn.cc`
- `src/emc/rs274ngc/gcodemodule.cc`
- `src/emc/rs274ngc/interpmodule.cc`
- `src/emc/tooldata/tooldata_mmap.cc`
- `src/emc/rs274ngc/interp_base.cc`
- `src/emc/rs274ngc/interp_o_word.cc`
- `src/emc/rs274ngc/rs274ngc_pre.cc`
## Boundaries
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-wasm-blockers-source-map.sh` before consuming blocker
analyzer output, source-linked LinuxCNC objects, or browser artifacts.
- `test-native.sh` and `test-linuxcnc-source-link.sh` must run
`test-linuxcnc-wasm-blockers.sh` with `CNC_SIM_WASM_BLOCKERS_SELF_CHECKS=1`
so bad manifests, missing LinuxCNC roots, duplicate entries, and missing
blocked-source replacements stay executable failures.
- The WASM blockers source-map guard validates LinuxCNC source/blocker
ownership and manifest drift only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-wasm-blockers-source-map.sh
./test-linuxcnc-wasm-blockers.sh linuxcnc-rs274-wasm-source-files.txt
./test-native.sh
./test-linuxcnc-source-link.sh
./build-wasm.sh
```
`check-linuxcnc-wasm-blockers-source-map.sh` keeps this document, the
RS274/WASM manifest, LinuxCNC source anchors, analyzer output, and wasm-safe
shim replacement set synchronized. Python/Boost.Python remap paths remain tracked blockers until they are ported, wrapped, or replaced by source-backed browser-safe LinuxCNC adaptations.

View File

@@ -0,0 +1,102 @@
# LinuxCNC WASM shims source map
This map records the source basis for the C++ sources in the
CMake `linuxcnc_wasm_safe_probe_shims` set. It is a build/source-link guard
only; it must not add CNC behavior and must not expand the temporary smoke parser.
## Generated Coverage
| Output | Count | Source coverage role |
| --- | ---: | --- |
| CMake `linuxcnc_wasm_safe_probe_shims` sources | 11 | Browser-safe LinuxCNC adaptation shims linked into WASM source probes. |
## Shim Map
| Shim | LinuxCNC source basis | Boundary |
| --- | --- | --- |
| `core/wasm_shims/dlfcn.cc` | `src/emc/rs274ngc/interp_base.cc` | Satisfies LinuxCNC `interp_base.cc` dynamic loader calls and RTLD flags for browser-safe probes while keeping dynamic loading unavailable. |
| `core/wasm_shims/emc_status_shim.cc` | `src/emc/sai/dummyemcstat.cc` | Provides the detached LinuxCNC `EMC_STAT` singleton shape without task/NML startup. |
| `core/wasm_shims/gettext_shim.cc` | `src/emc/rs274ngc/interp_internal.hh` | Preserves LinuxCNC `_()` diagnostic call sites by satisfying `gettext()` while leaving browser gettext catalogs unavailable. |
| `core/wasm_shims/linuxcnc_runtime_shim.cc` | `src/emc/rs274ngc/gcodemodule.cc`, `src/emc/task/emctaskmain.cc`, `src/emc/task/taskclass.cc`, `src/emc/rs274ngc/interp_namedparams.cc`, `include/hal.h`, `src/hal/hal_lib.c`, `src/emc/rs274ngc/rs274ngc_pre.cc` | Keeps preview/task, builtin module stubs, unavailable HAL lookup, and `wordexp()` edges compile-safe. |
| `core/wasm_shims/nml_status_shim.cc` | `src/libnml/nml/nmlmsg.cc`, `src/libnml/nml/stat_msg.cc` | Provides LinuxCNC NML status constructors, clear behavior, and `RCS_STAT_MSG` default status fields needed by source-linked EMC status objects. |
| `core/wasm_shims/python_c_api_shim.cc` | `src/emc/rs274ngc/interp_python.cc`, `src/emc/rs274ngc/interp_namedparams.cc`, `src/emc/rs274ngc/gcodemodule.cc`, `src/emc/pythonplugin/python_plugin.cc` | Satisfies Python C API symbols while Python/Boost.Python remap paths remain tracked blockers. |
| `core/wasm_shims/rcs_print_shim.cc` | `src/libnml/rcs/rcs_print.cc`, `src/libnml/rcs/rcs_print.hh` | Provides the LinuxCNC `rcs_print_error` split entry points used by NML validation without native print routing. |
| `core/wasm_shims/rtapi_compat.cc` | `src/rtapi/uspace_common.h`, `src/rtapi/rtapi.h` | Provides LinuxCNC userspace `rtapi_snprintf()` formatting by following the `rtapi.h` declaration and `uspace_common.h` `vsnprintf()` implementation path. |
| `core/wasm_shims/tooldata/tooldata_mmap_backend.cc` | `src/emc/tooldata/tooldata_mmap.cc` | Replaces native mmap storage with an in-memory WASM-safe backend for probes. |
| `core/wasm_shims/tooldata/tooldata_runtime_stubs.cc` | `src/emc/tooldata/tooldata_nml.cc`, `src/emc/tooldata/tooldata_db.cc` | Satisfies NML and external tool database entry points without native services, preserving inactive-DB return values. |
| `core/wasm_shims/pythonplugin/python_plugin.cc` | `src/emc/pythonplugin/python_plugin.cc`, `src/emc/pythonplugin/python_plugin.hh`, `src/emc/ini/inifile.cc` | Keeps LinuxCNC PythonPlugin API and negative-status short-circuit behavior available while Python execution is disabled for browser-safe probes. |
## Runtime Shim Boundaries
- `core/wasm_shims/dlfcn.cc` and `core/wasm_shims/dlfcn.h` cover the
LinuxCNC `interp_base.cc` dynamic loader calls to `dlopen()`, `dlsym()`,
`dlerror()`, `RTLD_GLOBAL`, and `RTLD_NOW`; their unavailable-loader returns
must keep handles and symbols null, keep `dlerror()` diagnostic text stable,
and leave `dlclose()` side-effect free.
- `core/wasm_shims/emc_status_shim.cc` must stay limited to the detached
LinuxCNC `EMC_STAT` singleton from `src/emc/sai/dummyemcstat.cc`; it must not
pull in task startup, NML channels, or operator callback behavior.
- `core/wasm_shims/linuxcnc_runtime_shim.cc` must keep preview `_task=0`,
matching `src/emc/rs274ngc/gcodemodule.cc`; it must not switch browser-safe probes into task mode.
- The runtime shim's builtin module stubs must match LinuxCNC's
`interpreter`/`emccanon` inittab shape from `gcodemodule.cc` and
`taskclass.cc` while returning null module pointers; they must not become
Python execution.
- The HAL lookup shim covers `hal_get_pin_value_by_name()`,
`hal_get_signal_value_by_name()`, and `hal_get_param_value_by_name()` from
`src/emc/rs274ngc/interp_namedparams.cc`, `include/hal.h`, and
`src/hal/hal_lib.c`; it must leave HAL values unavailable by returning `-1`,
`HAL_TYPE_UNINITIALIZED`, and null data pointers.
- `wordexp()` support is limited to the single-word `wordexp()` path expansion
and `wordfree()` cleanup needed by `src/emc/rs274ngc/rs274ngc_pre.cc` for
basename, program prefix, and subroutine-directory lookups; it must not become a shell expansion feature.
## Python Shim Boundaries
- `core/wasm_shims/python_c_api_shim.cc` satisfies Python C API symbols used by
LinuxCNC RS274/remap sources; it must not become Python execution.
- The Python C API shim must return inert objects, null exceptions, false
callable/type checks, and successful module/type registration statuses only
to satisfy LinuxCNC `interp_python.cc`, `gcodemodule.cc`, and
`python_plugin.cc` link expectations; it must not call into CPython runtime.
- `core/wasm_shims/pythonplugin/python_plugin.cc` must preserve LinuxCNC
`PythonPlugin` status edges for `PLUGIN_NO_SECTION`, `PLUGIN_BAD_INIFILE`,
`PLUGIN_BAD_PATH`, `PLUGIN_INITTAB_FAILED`,
`PLUGIN_PYTHON_NOT_INITIALIZED`, `PLUGIN_NO_CALLABLE`, and
`PLUGIN_EXCEPTION` while keeping Python execution disabled.
- The PythonPlugin shim must preserve the first-caller `_inittab` `PLUGIN_INITTAB_FAILED` edge from `src/emc/pythonplugin/python_plugin.cc` when a built-in module init function fails, while keeping browser-safe probes detached from real Python module execution.
- The PythonPlugin shim must preserve the `TOPLEVEL` missing-path `PLUGIN_BAD_PATH` edge from `src/emc/pythonplugin/python_plugin.cc` before reporting the browser-safe disabled Python runtime status.
- The PythonPlugin shim must preserve `PATH_PREPEND` and `PATH_APPEND` tilde-expansion failure statuses from `src/emc/pythonplugin/python_plugin.cc` and `src/emc/ini/inifile.cc` without executing Python path mutation in browser-safe probes.
- The PythonPlugin shim must preserve LinuxCNC INI section-name trimming and trailing section-comment handling from `src/emc/ini/inifile.cc` for Python/remap configuration keys.
- The PythonPlugin shim must not execute `bp::exec`, `bp::exec_file`, or real
Python callable dispatch; `PyObject_Call` is an inert C API symbol only.
Browser Python execution remains blocked until implemented as a source-backed
browser-safe LinuxCNC adaptation.
## Boundaries
- The CMake `linuxcnc_wasm_safe_probe_shims` set and `list-linuxcnc-wasm-safe-shims.sh` must stay synchronized.
- Each shim source must keep a nearby `LinuxCNC source basis:` comment naming the LinuxCNC source it adapts.
- Every LinuxCNC source named above must remain tracked in the RS274/WASM manifest as `core`, `header`, or `metadata`.
- These shims are browser-safe LinuxCNC adaptations for source-link probes; they are not new interpreter behavior.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-wasm-shims-source-map.sh` before consuming shim-backed
source-linked LinuxCNC objects or browser artifacts.
- The WASM shims source-map guard validates LinuxCNC source/shim ownership and
manifest drift only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-wasm-shims-source-map.sh
./test-linuxcnc-wasm-cmake-safe-probe.sh
./test-native.sh
./test-linuxcnc-source-link.sh
./build-wasm.sh
```
`check-linuxcnc-wasm-shims-source-map.sh` keeps this document, the CMake shim
set, `list-linuxcnc-wasm-safe-shims.sh`, LinuxCNC source anchors, and the
RS274/WASM manifest synchronized.

View File

@@ -0,0 +1,79 @@
# LinuxCNC WASM smoke source map
This map records the source basis and boundaries for the Node and browser WASM
smoke wrappers. It is a smoke-test guard only; it must not add CNC behavior,
must not expand the temporary smoke parser, and must keep browser filesystem
persistence OPFS-only. In browser contexts, browser filesystem persistence
OPFS-only is the required boundary.
## LinuxCNC Source Basis
- `src/emc/rs274ngc/rs274ngc_pre.cc` provides `Interp::ini_load()`,
`Interp::restore_parameters()`, `Interp::save_parameters()`, and `read_text()`
behavior exercised by Node/browser bridge tests.
- `src/emc/rs274ngc/rs274ngc_pre.cc` saves parameters by writing
`filename + ".new"`, unlinking `filename + ".bak"`, linking the previous file
to `filename + ".bak"`, and renaming the temporary file over the main file.
- `src/emc/rs274ngc/interp_convert.cc` provides `convert_m()` coverage for
M66 waits and M68 analog output side effects used by generated remap cases.
- `src/emc/nml_intf/canon.hh` declares the `WAIT()` and
`SET_AUX_OUTPUT_VALUE()` canon entry points surfaced through the event bridge.
- `src/emc/kinematics/5axiskins.c`, `src/emc/kinematics/trtfuncs.c`,
`src/emc/kinematics/xyzac-trt-kins.c`,
`src/emc/kinematics/xyzbc-trt-kins.c`, and
`src/emc/kinematics/userkfuncs.c` provide the RTCP/kinematics inverse paths
covered by WASM smoke tests.
- `configs/sim/**.ini` files and `remap_subs/{428,429,430}remap.ngc` sources
generate `web/public/linuxcnc_switchkins_remap_config_cases.json`.
## Smoke Coverage
| Smoke entry | Boundary |
| --- | --- |
| `test-web-wasm-node-smoke.sh` | Runs the Node WASM wrapper with generated LinuxCNC switchkins cases and LinuxCNC-backed RTCP checks. |
| `test-web-wasm-node-smoke.cjs` | Exercises the C ABI, `web/src/wasm-core.js`, generated switchkins config cases, and source-backed kinematics events. |
| `test-web-wasm-browser-smoke.sh` | Runs browser checks after verifying copied WASM artifacts and smoke module structure. |
| `web/test-browser-wasm-smoke-linuxcnc-sections.js` | Exercises LinuxCNC browser parsing and generated switchkins cases without INI_FILE_NAME remap-load aliases. |
| `web/test-browser-wasm-smoke-opfs-workspace-sections.js` | Aggregates OPFS basic, parameter, mirror, and directory sections under browser OPFS availability. |
| `web/test-browser-wasm-smoke-opfs-basic-sections.js` | Exercises OPFS-backed program file reads through LinuxCNC `read_text()` and rejects unsafe paths. |
| `web/test-browser-wasm-smoke-opfs-parameter-sections.js` | Exercises LinuxCNC parameter restore/save/backup semantics through OPFS. |
| `web/test-browser-wasm-smoke-opfs-mirror-sections.js` | Exercises OPFS/WASM mirror persistence used by source-backed program parsing and parameter movement. |
| `web/test-browser-wasm-smoke-opfs-directory-sections.js` | Exercises OPFS directory persistence and WASM mirror refresh without filesystem persistence outside OPFS. |
| `web/test-browser-wasm-smoke-opfs-policy-sections.js` | Exercises browser OPFS workspace and mount policy, including the rule that OPFS cannot be disabled. |
| `web/test-browser-wasm-smoke-app-sections.js` | Verifies the browser app uses OPFS program and parameter persistence. |
## Boundaries
- Node smoke must leave OPFS disabled and must reject `parseFile*` methods that
require an OPFS workspace.
- Browser smoke must keep program files, LinuxCNC parameter files, temporary
`.new` files, and `.bak` backups under OPFS-backed storage.
- Browser smoke harness files must not use Local Storage, IndexedDB, File
Picker, Directory Picker, WebKit filesystem, or FileReader persistence paths.
- Generated switchkins config smoke cases must continue to come from
`web/public/linuxcnc_switchkins_remap_config_cases.json`.
- The browser smoke shell guard also pins required section names for LinuxCNC
browser parsing, OPFS workspace/policy/directory handling, OPFS parameter
restore/save failure handling, and browser app OPFS reload coverage.
- Smoke wrappers must keep positive minimum coverage thresholds so accidental
section loss is visible.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run `./check-linuxcnc-wasm-smoke-source-map.sh` before consuming Node/browser
WASM smoke coverage or browser artifacts.
- The WASM smoke source-map guard validates LinuxCNC source-backed smoke
coverage only; it does not interpret G-code.
## Checks
Run:
```bash
./check-linuxcnc-wasm-smoke-source-map.sh
./test-native.sh
./test-linuxcnc-source-link.sh
./build-wasm.sh
```
`check-linuxcnc-wasm-smoke-source-map.sh` keeps this document, LinuxCNC source
anchors, smoke wrapper boundaries, OPFS-only policy, and generated switchkins
case usage synchronized.

View File

@@ -0,0 +1,59 @@
# LinuxCNC workstreams source map
This map ties the six active workstream contexts to their source-map guards. It
is a coordination guard only; it must not add CNC behavior and must not expand
the temporary smoke parser.
## Workstream Coverage
| Workstream | Source-map documents | Guard scripts |
| --- | --- | --- |
| `switchkins/remap` generator and tables | `docs/linuxcnc-switchkins-remap-source-map.md` | `check-linuxcnc-switchkins-remap-source-map.sh` |
| `linuxcnc-kinematics` source manifest | `docs/linuxcnc-kinematics-source-map.md` | `check-linuxcnc-kinematics-source-map.sh` |
| WASM filesystem and OPFS persistence | `docs/linuxcnc-opfs-source-map.md`, `docs/linuxcnc-wasm-shims-source-map.md`, `docs/linuxcnc-wasm-blockers-source-map.md`, `docs/linuxcnc-tooldata-source-map.md` | `check-linuxcnc-opfs-source-map.sh`, `check-linuxcnc-wasm-shims-source-map.sh`, `check-linuxcnc-wasm-blockers-source-map.sh`, `check-linuxcnc-tooldata-source-map.sh` |
| browser app integration | `docs/linuxcnc-browser-app-source-map.md` | `check-linuxcnc-browser-app-source-map.sh` |
| Node and browser smoke coverage | `docs/linuxcnc-wasm-smoke-source-map.md` | `check-linuxcnc-wasm-smoke-source-map.sh` |
| source-link, build, and documentation constraints | `docs/linuxcnc-rs274-source-map.md`, `docs/linuxcnc-build-source-map.md`, `docs/linuxcnc-tooldata-source-map.md` | `check-linuxcnc-rs274-source-map.sh`, `check-linuxcnc-build-source-map.sh`, `check-linuxcnc-tooldata-source-map.sh` |
## LinuxCNC Source Basis
- `configs/sim/**.ini` and `remap_subs/{428,429,430}remap.ngc` back the
switchkins/remap workstream.
- `src/emc/kinematics/Submakefile` and `src/emc/kinematics/kinematics.h` back
the kinematics manifest workstream.
- `src/emc/rs274ngc/interp_internal.hh` and
`src/emc/rs274ngc/rs274ngc_pre.cc` back the OPFS/browser parameter-file
workstreams.
- `src/emc/rs274ngc/Submakefile`, `src/emc/tooldata/Submakefile`,
`src/emc/ini/Submakefile`, and `src/emc/pythonplugin/Submakefile` back the
source-link and build-policy workstream.
## Boundaries
- Every active workstream must have at least one source-map document and guard
script.
- `docs/linuxcnc-source-policy.md` must keep the same one-context workflow
boundary and smoke-verification-only rule used by this index.
- `test-native.sh`, `test-linuxcnc-source-link.sh`, and `build-wasm.sh` must
run the workstream source-map guard before consuming generated config aliases,
source-linked LinuxCNC objects, or browser artifacts.
- WASM blocker and shim maps remain source coverage guards, not browser API
expansion points.
- Browser filesystem persistence remains OPFS-only.
- Smoke coverage remains verification only and must not become a functional CNC
behavior path.
## Checks
Run:
```bash
./check-linuxcnc-workstreams-source-map.sh
./test-native.sh
./test-linuxcnc-source-link.sh
./build-wasm.sh
```
`check-linuxcnc-workstreams-source-map.sh` keeps the six-workstream architecture
list, source-map documents, guard scripts, LinuxCNC source anchors, and native /
source-link / build-wasm guard entry points synchronized.

View File

@@ -141,6 +141,47 @@ grep -F 'CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY must be 0 or 1: $guardrails_only' te
grep -F 'all native guardrail checks passed' test-all-native.sh >/dev/null
grep -F 'LinuxCNC source basis: the wasm build preflights and compiles the' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: manifest validation checks LinuxCNC-root relative' check-linuxcnc-inputs.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard keeps the local source map aligned' check-linuxcnc-rs274-source-map.sh >/dev/null
grep -F './check-linuxcnc-rs274-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-rs274-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard keeps the kinematics source map aligned' check-linuxcnc-kinematics-source-map.sh >/dev/null
grep -F './check-linuxcnc-kinematics-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-kinematics-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties browser OPFS parameter persistence' check-linuxcnc-opfs-source-map.sh >/dev/null
grep -F './check-linuxcnc-opfs-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-opfs-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties generated switchkins/remap aliases' check-linuxcnc-switchkins-remap-source-map.sh >/dev/null
grep -F './check-linuxcnc-switchkins-remap-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-switchkins-remap-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F './check-linuxcnc-switchkins-remap-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties Node/browser WASM smoke coverage' check-linuxcnc-wasm-smoke-source-map.sh >/dev/null
grep -F './check-linuxcnc-wasm-smoke-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-wasm-smoke-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F './check-linuxcnc-wasm-smoke-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties native/source-link/WASM build entry' check-linuxcnc-build-source-map.sh >/dev/null
grep -F './check-linuxcnc-build-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-build-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F './check-linuxcnc-build-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties the browser app entry point' check-linuxcnc-browser-app-source-map.sh >/dev/null
grep -F './check-linuxcnc-browser-app-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-browser-app-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F './check-linuxcnc-browser-app-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties the six active workstream contexts' check-linuxcnc-workstreams-source-map.sh >/dev/null
grep -F './check-linuxcnc-workstreams-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-workstreams-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F './check-linuxcnc-workstreams-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties LinuxCNC tooldata/Submakefile' check-linuxcnc-tooldata-source-map.sh >/dev/null
grep -F './check-linuxcnc-tooldata-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-tooldata-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F './check-linuxcnc-tooldata-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties the documented WASM blocker map' check-linuxcnc-wasm-blockers-source-map.sh >/dev/null
grep -F './check-linuxcnc-wasm-blockers-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-wasm-blockers-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F './check-linuxcnc-wasm-blockers-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: this guard ties the documented wasm-safe shim map' check-linuxcnc-wasm-shims-source-map.sh >/dev/null
grep -F './check-linuxcnc-wasm-shims-source-map.sh' test-native.sh >/dev/null
grep -F './check-linuxcnc-wasm-shims-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F './check-linuxcnc-wasm-shims-source-map.sh' build-wasm.sh >/dev/null
grep -F 'LinuxCNC source basis: the cached input checker keys check-linuxcnc-inputs.sh' check-linuxcnc-inputs-cached.sh >/dev/null
grep -F 'LinuxCNC source basis: switchkins/remap table validation derives M428/M429/M430' check-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest" --require-kinematics-complete' check-linuxcnc-switchkins-remap-table.sh >/dev/null
@@ -203,6 +244,39 @@ grep -F '`src/hal/utils/halcmd_completion.c`' docs/linuxcnc-source-policy.md >/d
grep -F 'must not become new generated API aliases or browser cases.' docs/linuxcnc-source-policy.md >/dev/null
grep -F 'The kinematics manifest lister must preserve every allowed manifest group' docs/linuxcnc-source-policy.md >/dev/null
grep -F 'including `asset`, `tooldata`, and' docs/linuxcnc-source-policy.md >/dev/null
grep -F 'LinuxCNC kinematics source map' docs/linuxcnc-kinematics-source-map.md >/dev/null
grep -F '| `tooldata` | 10 | LinuxCNC tool table files referenced by covered M428/M429/M430 INI files. |' docs/linuxcnc-kinematics-source-map.md >/dev/null
grep -F 'Browser-side file persistence remains OPFS-only' docs/linuxcnc-kinematics-source-map.md >/dev/null
grep -F 'LinuxCNC OPFS source map' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F '`Interp::save_parameters()` for `filename + ".new"`' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F 'Browser contexts with OPFS support must not disable OPFS.' docs/linuxcnc-opfs-source-map.md >/dev/null
grep -F 'LinuxCNC switchkins/remap source map' docs/linuxcnc-switchkins-remap-source-map.md >/dev/null
grep -F '| `core/src/linuxcnc_switchkins_remap_table.inc` | 177 | Generated switchkins aliases from LinuxCNC INI/HAL/remap sources. |' docs/linuxcnc-switchkins-remap-source-map.md >/dev/null
grep -F '[EMCIO] TOOL_TABLE` entries are source coverage only' docs/linuxcnc-switchkins-remap-source-map.md >/dev/null
grep -F './check-linuxcnc-switchkins-remap-source-map.sh' test-linuxcnc-source-link.sh >/dev/null
grep -F 'LinuxCNC WASM blockers source map' docs/linuxcnc-wasm-blockers-source-map.md >/dev/null
grep -F '| `blocked-replacements` | 9 | Blocked LinuxCNC sources mapped to wasm-safe shim files. |' docs/linuxcnc-wasm-blockers-source-map.md >/dev/null
grep -F 'Python/Boost.Python remap paths remain tracked blockers' docs/linuxcnc-wasm-blockers-source-map.md >/dev/null
grep -F 'LinuxCNC WASM shims source map' docs/linuxcnc-wasm-shims-source-map.md >/dev/null
grep -F '| CMake `linuxcnc_wasm_safe_probe_shims` sources | 11 | Browser-safe LinuxCNC adaptation shims linked into WASM source probes. |' docs/linuxcnc-wasm-shims-source-map.md >/dev/null
grep -F 'must not expand the temporary smoke parser' docs/linuxcnc-wasm-shims-source-map.md >/dev/null
grep -F 'LinuxCNC WASM smoke source map' docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F 'Node smoke must leave OPFS disabled' docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F 'Generated switchkins config smoke cases must continue to come from' docs/linuxcnc-wasm-smoke-source-map.md >/dev/null
grep -F 'LinuxCNC build source map' docs/linuxcnc-build-source-map.md >/dev/null
grep -F 'Default WASM manifest partition must remain 26 wasm-safe `core` sources' docs/linuxcnc-build-source-map.md >/dev/null
grep -F '`build-wasm.sh` must configure with `CNC_SIM_ENABLE_SMOKE_BACKEND=OFF`' docs/linuxcnc-build-source-map.md >/dev/null
grep -F 'LinuxCNC browser app source map' docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F 'The app must persist the current program at `programs/current.ngc`.' docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F 'The visible backend selector must stay limited to the LinuxCNC RS274 backend.' docs/linuxcnc-browser-app-source-map.md >/dev/null
grep -F 'LinuxCNC workstreams source map' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F '| `switchkins/remap` generator and tables | `docs/linuxcnc-switchkins-remap-source-map.md` |' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'Every active workstream must have at least one source-map document and guard' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'docs/linuxcnc-source-policy.md` must keep the same one-context workflow' docs/linuxcnc-workstreams-source-map.md >/dev/null
grep -F 'Smoke tests are verification guardrails. They must not be expanded with new' docs/linuxcnc-source-policy.md >/dev/null
grep -F 'LinuxCNC tooldata source map' docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F '`src/emc/tooldata/Submakefile` selects `tooldata_mmap.cc`' docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F 'Tool table files referenced by switchkins INI files are source coverage only' docs/linuxcnc-tooldata-source-map.md >/dev/null
grep -F 'check-linuxcnc-switchkins-remap-table-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-kinematics-source-files.txt' test-native.sh >/dev/null
grep -F 'check-linuxcnc-switchkins-remap-table-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-kinematics-source-files.txt' test-linuxcnc-source-link.sh >/dev/null
grep -F 'check-linuxcnc-switchkins-remap-table-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-kinematics-source-files.txt' build-wasm.sh >/dev/null
@@ -853,38 +927,98 @@ wasm_manifest_blocked = [
for line in Path("linuxcnc-rs274-wasm-source-files.txt").read_text(encoding="utf-8").splitlines()
if line.startswith("blocked:")
]
wasm_manifest_headers = [
line.split(":", 2)[1]
for line in Path("linuxcnc-rs274-wasm-source-files.txt").read_text(encoding="utf-8").splitlines()
if line.startswith("header:")
]
wasm_manifest_blocked_headers = [
line.split(":", 2)[1]
for line in Path("linuxcnc-rs274-wasm-source-files.txt").read_text(encoding="utf-8").splitlines()
if line.startswith("blocked-header:")
]
wasm_manifest_metadata = [
line.split(":", 2)[1]
for line in Path("linuxcnc-rs274-wasm-source-files.txt").read_text(encoding="utf-8").splitlines()
if line.startswith("metadata:")
]
source_map_core = []
source_map_bindings = []
source_map_wasm_core = []
source_map_wasm_blocked = []
source_map_wasm_headers = []
source_map_wasm_blocked_headers = []
source_map_wasm_metadata = []
in_core_section = False
in_binding_section = False
in_wasm_core_section = False
in_wasm_blocked_section = False
in_wasm_header_section = False
in_wasm_blocked_header_section = False
in_wasm_metadata_section = False
for line in source_map.splitlines():
if line == "### Interpreter core":
in_core_section = True
in_binding_section = False
in_wasm_core_section = False
in_wasm_blocked_section = False
in_wasm_header_section = False
in_wasm_blocked_header_section = False
in_wasm_metadata_section = False
continue
if line == "### Python binding modules":
in_core_section = False
in_binding_section = True
in_wasm_core_section = False
in_wasm_blocked_section = False
in_wasm_header_section = False
in_wasm_blocked_header_section = False
in_wasm_metadata_section = False
continue
if line == "### WASM-safe source core":
in_core_section = False
in_binding_section = False
in_wasm_core_section = True
in_wasm_blocked_section = False
in_wasm_header_section = False
in_wasm_blocked_header_section = False
in_wasm_metadata_section = False
continue
if line == "### WASM-blocked sources":
in_core_section = False
in_binding_section = False
in_wasm_core_section = False
in_wasm_blocked_section = True
in_wasm_header_section = False
in_wasm_blocked_header_section = False
in_wasm_metadata_section = False
continue
if line == "### WASM-tracked headers":
in_core_section = False
in_binding_section = False
in_wasm_core_section = False
in_wasm_blocked_section = False
in_wasm_header_section = True
in_wasm_blocked_header_section = False
in_wasm_metadata_section = False
continue
if line == "### WASM-blocked headers":
in_core_section = False
in_binding_section = False
in_wasm_core_section = False
in_wasm_blocked_section = False
in_wasm_header_section = False
in_wasm_blocked_header_section = True
in_wasm_metadata_section = False
continue
if line == "### WASM metadata sources":
in_core_section = False
in_binding_section = False
in_wasm_core_section = False
in_wasm_blocked_section = False
in_wasm_header_section = False
in_wasm_blocked_header_section = False
in_wasm_metadata_section = True
continue
if in_core_section and line.startswith("### "):
break
@@ -894,6 +1028,12 @@ for line in source_map.splitlines():
in_wasm_core_section = False
if in_wasm_blocked_section and line.startswith("### "):
in_wasm_blocked_section = False
if in_wasm_header_section and line.startswith("### "):
in_wasm_header_section = False
if in_wasm_blocked_header_section and line.startswith("### "):
in_wasm_blocked_header_section = False
if in_wasm_metadata_section and line.startswith("### "):
in_wasm_metadata_section = False
if in_core_section and line.startswith("- `"):
source_map_core.append(line.split("`", 2)[1])
if in_binding_section and line.startswith("- `"):
@@ -902,6 +1042,12 @@ for line in source_map.splitlines():
source_map_wasm_core.append(line.split("`", 2)[1])
if in_wasm_blocked_section and line.startswith("- `"):
source_map_wasm_blocked.append(line.split("`", 2)[1])
if in_wasm_header_section and line.startswith("- `"):
source_map_wasm_headers.append(line.split("`", 2)[1])
if in_wasm_blocked_header_section and line.startswith("- `"):
source_map_wasm_blocked_headers.append(line.split("`", 2)[1])
if in_wasm_metadata_section and line.startswith("- `"):
source_map_wasm_metadata.append(line.split("`", 2)[1])
if source_map_core != manifest_core:
raise SystemExit(
"docs/linuxcnc-rs274-source-map.md interpreter core list no longer matches linuxcnc-rs274-source-files.txt core manifest"
@@ -918,6 +1064,18 @@ if source_map_wasm_blocked != wasm_manifest_blocked:
raise SystemExit(
"docs/linuxcnc-rs274-source-map.md WASM-blocked source list no longer matches linuxcnc-rs274-wasm-source-files.txt blocked manifest"
)
if source_map_wasm_headers != wasm_manifest_headers:
raise SystemExit(
"docs/linuxcnc-rs274-source-map.md WASM-tracked header list no longer matches linuxcnc-rs274-wasm-source-files.txt header manifest"
)
if source_map_wasm_blocked_headers != wasm_manifest_blocked_headers:
raise SystemExit(
"docs/linuxcnc-rs274-source-map.md WASM-blocked header list no longer matches linuxcnc-rs274-wasm-source-files.txt blocked-header manifest"
)
if source_map_wasm_metadata != wasm_manifest_metadata:
raise SystemExit(
"docs/linuxcnc-rs274-source-map.md WASM metadata source list no longer matches linuxcnc-rs274-wasm-source-files.txt metadata manifest"
)
PY
for script in \
@@ -1894,6 +2052,9 @@ if ! grep -F "missing manifest source: src/emc/rs274ngc/does_not_exist.cc" "$mis
exit 1
fi
./check-linuxcnc-wasm-blockers-source-map.sh linuxcnc-rs274-wasm-source-files.txt
./check-linuxcnc-wasm-shims-source-map.sh linuxcnc-rs274-wasm-source-files.txt
if [[ "$guardrails_only" == "1" ]]; then
echo "all native guardrail checks passed"
exit 0

View File

@@ -42,6 +42,10 @@ flock 9
var_file="$build_dir/rs274ngc.var"
base_var_file="$build_dir/rs274ngc-base.var"
ini_named_parameter_file="$(pwd)/tests/gcode/linuxcnc_ini_named_parameter.ini"
disable_g92_persistence_ini_file="$(pwd)/tests/gcode/linuxcnc_disable_g92_persistence.ini"
disable_fanuc_style_sub_ini_file="$(pwd)/tests/gcode/linuxcnc_disable_fanuc_style_sub.ini"
tight_arc_center_tolerance_ini_file="$(pwd)/tests/gcode/linuxcnc_tight_arc_center_tolerance.ini"
g64_default_tolerances_ini_file="$(pwd)/tests/gcode/linuxcnc_g64_default_tolerances.ini"
output_dir="$build_dir/outputs"
mkdir -p "$output_dir"
cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$base_var_file"
@@ -495,10 +499,14 @@ for case in "${threading_check_error_cases[@]}"; do
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
# convert_straight() and convert_threading_cycle() reject invalid
# spindle state/selector, rotary-axis motion, and G76 I/J/K geometry
# before issuing synchronized threading motion.
# convert_arc(), convert_straight(), and convert_threading_cycle() reject invalid
# feed/spindle state, spindle selectors, rotary-axis motion, and G76 I/J/K
# geometry before issuing arc, straight, or synchronized threading motion.
threading_convert_error_cases=(
"linuxcnc_arc_inverse_time_missing_f_error|F word missing with inverse time arc move"
"linuxcnc_g1_zero_feed_error|Cannot do g1 with zero feed rate"
"linuxcnc_g1_inverse_time_missing_f_error|F word missing with inverse time g1 move"
"linuxcnc_g95_zero_spindle_speed_error|Cannot feed with zero spindle speed in feed per rev mode"
"linuxcnc_g33_stopped_spindle_error|Spindle not turning in G33"
"linuxcnc_g33_1_stopped_spindle_error|Spindle not turning in G33.1"
"linuxcnc_g33_spindle_dollar_out_of_range_error|Invalid spindle (\$) number in G33 move"
@@ -521,6 +529,26 @@ for case in "${threading_convert_error_cases[@]}"; do
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_probe()
# rejects active cutter compensation, zero feed, feed-per-revolution mode, and
# degenerate probe moves before issuing STRAIGHT_PROBE().
probe_convert_error_cases=(
"linuxcnc_probe_cutter_comp_error|Cannot probe with cutter radius comp on"
"linuxcnc_probe_zero_feed_error|Cannot probe with zero feed rate"
"linuxcnc_probe_feed_per_rev_error|Cannot probe with feed per rev mode"
"linuxcnc_probe_start_too_close_error|Start point too close to probe point"
)
for case in "${probe_convert_error_cases[@]}"; do
fixture=${case%%|*}
expected=${case#*|}
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_dump" "tests/gcode/$fixture.ngc" \
>"$output_dir/cnc_sim_linuxcnc_$fixture.json" 2>"$output_dir/cnc_sim_linuxcnc_$fixture.err"; then
echo "expected $fixture.ngc to fail" >&2
exit 1
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_$fixture.err"
done
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_threading_sync.ngc >"$output_dir/cnc_sim_linuxcnc_threading_sync.json"
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_threading_cycle.ngc >"$output_dir/cnc_sim_linuxcnc_threading_cycle.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
@@ -565,6 +593,14 @@ CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linux
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coolant.ngc >"$output_dir/cnc_sim_linuxcnc_coolant.json"
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_feed_modes.ngc >"$output_dir/cnc_sim_linuxcnc_feed_modes.json"
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_motion_modes.ngc >"$output_dir/cnc_sim_linuxcnc_motion_modes.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
# convert_control_mode() sends G64 P through SET_MOTION_CONTROL_MODE()
# and G64 Q through SET_NAIVECAM_TOLERANCE(), defaulting Q to P when Q is omitted.
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_control_mode_tolerances.ngc >"$output_dir/cnc_sim_linuxcnc_control_mode_tolerances.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [RS274NGC] G64_DEFAULT_TOLERANCE/G64_DEFAULT_NAIVETOLERANCE, and
# interp_convert.cc convert_control_mode() uses them when G64 omits P/Q.
INI_FILE_NAME="$g64_default_tolerances_ini_file" CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_g64_default_tolerances.ngc >"$output_dir/cnc_sim_linuxcnc_g64_default_tolerances.json"
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_tool_length.ngc >"$output_dir/cnc_sim_linuxcnc_tool_length.json"
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_dynamic_tool_length.ngc >"$output_dir/cnc_sim_linuxcnc_dynamic_tool_length.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
@@ -591,9 +627,11 @@ done
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycle.ngc >"$output_dir/cnc_sim_linuxcnc_canned_cycle.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_cycles.cc
# convert_cycle() and the G73/G74/G82-G89 helpers validate canned-cycle
# repeat counts, plane-specific axis/R words, Q/P requirements, and spindle
# state before expanding cycles to Canon motion.
# feed mode, repeat counts, plane-specific axis/R words, Q/P requirements,
# and spindle state before expanding cycles to Canon motion.
canned_cycle_error_cases=(
"linuxcnc_canned_cycle_inverse_time_error|Cannot use inverse time feed with canned cycles"
"linuxcnc_canned_cycle_cutter_comp_error|Cannot use canned cycles with cutter compensation on"
"linuxcnc_canned_cycle_l0_error|Cannot do zero repeats of cycle"
"linuxcnc_canned_cycle_missing_r_error|R clearance plane unspecified in cycle"
"linuxcnc_canned_cycle_missing_z_error|Z value unspecified in xy plane canned cycle"
@@ -656,6 +694,15 @@ CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linux
# passes unit-specific center-arc tolerance to src/emc/rs274ngc/interp_arc.cc
# arc_data_ijk(), which rejects excessive start/end radius mismatch.
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_arc_center_tolerance_good_imperial.ngc >"$output_dir/cnc_sim_linuxcnc_arc_center_tolerance_good_imperial.json"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_arc_center_tolerance_tight_metric.ngc >"$output_dir/cnc_sim_linuxcnc_arc_center_tolerance_tight_metric_default.json"
if INI_FILE_NAME="$tight_arc_center_tolerance_ini_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_arc_center_tolerance_tight_metric.ngc \
>"$output_dir/cnc_sim_linuxcnc_arc_center_tolerance_tight_metric_ini.json" 2>"$output_dir/cnc_sim_linuxcnc_arc_center_tolerance_tight_metric_ini.err"; then
echo "expected linuxcnc_arc_center_tolerance_tight_metric.ngc to fail with tight INI tolerance" >&2
exit 1
fi
grep -Fq "Radius to end of arc differs from radius to start" \
"$output_dir/cnc_sim_linuxcnc_arc_center_tolerance_tight_metric_ini.err"
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_arc_center_tolerance_bad_metric.ngc \
>"$output_dir/cnc_sim_linuxcnc_arc_center_tolerance_bad_metric.json" 2>"$output_dir/cnc_sim_linuxcnc_arc_center_tolerance_bad_metric.err"; then
@@ -716,11 +763,19 @@ CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linux
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_machine_coordinates.ngc >"$output_dir/cnc_sim_linuxcnc_machine_coordinates.json"
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_cutter_comp.ngc >"$output_dir/cnc_sim_linuxcnc_cutter_comp.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc rejects
# M6, M66, G71.0, and G98/G99 while cutter compensation is active;
# compensated corner and G40 exit checks also reject gouging/arc exits.
# G28/G30, G52/G92, G53, G54-G59.3, G61/G64, G17/G18/G19, M6, M66, G43,
# G71.0, and G98/G99 while cutter compensation is active; compensated corner
# and G40 exit checks also reject gouging/arc exits.
cutter_comp_execution_error_cases=(
"linuxcnc_cutter_comp_axis_offset_error|Cannot change axis offsets with cutter radius comp"
"linuxcnc_cutter_comp_control_mode_error|Cannot change control mode with cutter radius compensation on"
"linuxcnc_cutter_comp_coordinate_system_error|Cannot change coordinate systems with cutter radius compensation on"
"linuxcnc_cutter_comp_g53_error|Cannot use g53 with cutter radius comp"
"linuxcnc_cutter_comp_home_error|Cannot use g28 or g30 with cutter radius comp"
"linuxcnc_cutter_comp_plane_change_error|Cannot change planes with cutter radius compensation on"
"linuxcnc_cutter_comp_tool_change_error|Cannot change tools with cutter radius compensation on"
"linuxcnc_cutter_comp_wait_input_error|Cannot wait for digital input with cutter radius compensation on"
"linuxcnc_cutter_comp_tool_offset_error|Cannot change tool offset with cutter radius compensation on"
"linuxcnc_cutter_comp_g71_error|G71.0 cannot be used with cutter compensation enabled"
"linuxcnc_canned_cycle_g98_retract_error|Cannot change retract mode with cutter radius compensation on"
"linuxcnc_ccomp_arcexit_error|The move just after exiting cutter compensation mode must be straight, not an arc"
@@ -784,6 +839,25 @@ for case in "${oword_reader_error_cases[@]}"; do
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [RS274NGC] DISABLE_FANUC_STYLE_SUB, and interp_read.cc rejects
# Fanuc-style M98/M99 and bare O-word subroutine forms when enabled.
disable_fanuc_style_error_cases=(
"linuxcnc_disable_fanuc_style_m98_error|DISABLE_FANUC_STYLE_SUB set in INI file, but found m98"
"linuxcnc_disable_fanuc_style_m99_error|DISABLE_FANUC_STYLE_SUB set in INI file, but found m99"
"linuxcnc_disable_fanuc_style_bare_oword_error|DISABLE_FANUC_STYLE_SUB disabled in INI file, but found bare O-word"
)
for case in "${disable_fanuc_style_error_cases[@]}"; do
fixture=${case%%|*}
expected=${case#*|}
if INI_FILE_NAME="$disable_fanuc_style_sub_ini_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_dump" "tests/gcode/$fixture.ngc" \
>"$output_dir/cnc_sim_linuxcnc_$fixture.json" 2>"$output_dir/cnc_sim_linuxcnc_$fixture.err"; then
echo "expected $fixture.ngc to fail" >&2
exit 1
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_o_word.cc
# convert_control_functions() rejects unmatched, duplicate, and
# out-of-context O-word control labels during execution. control_save_offset()
@@ -841,9 +915,11 @@ INI_FILE_NAME="$ini_named_parameter_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS27
# and close_and_downcase() accept whitespace around bracketed word values.
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_word_bracket_whitespace.ngc >"$output_dir/cnc_sim_linuxcnc_word_bracket_whitespace.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_namedparams.cc lookup_named_param()
# and init_readonly_param() expose modal/unit/feed/spindle readonly parameters.
# and init_readonly_param() expose modal/unit/feed/spindle readonly parameters
# plus current-position #<_x> through #<_w> aliases.
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_readonly_named_parameters.ngc >"$output_dir/cnc_sim_linuxcnc_readonly_named_parameters.json"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_readonly_named_parameters_extra.ngc >"$output_dir/cnc_sim_linuxcnc_readonly_named_parameters_extra.json"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_readonly_named_current_position.ngc >"$output_dir/cnc_sim_linuxcnc_readonly_named_current_position.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_array.cc defines
# required/readonly numbered parameters; interp_internal.cc set_probe_data()
# and interp_convert.cc update probe, tool, G28/G30, G92, G5X, and current
@@ -884,6 +960,17 @@ cp "$base_var_file" "$var_file"
# save_parameters() writes required and changed persistent ranges.
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_persistent_parameters_set.ngc >"$output_dir/cnc_sim_linuxcnc_persistent_parameters_set.json"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_persistent_parameters_check.ngc >"$output_dir/cnc_sim_linuxcnc_persistent_parameters_check.json"
disable_g92_startup_var_file="$build_dir/rs274ngc-disable-g92-startup.var"
printf '5210\t1.000000\n5211\t-4.000000\n5212\t-5.000000\n5213\t-6.000000\n5220\t1.000000\n' > "$disable_g92_startup_var_file"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [RS274NGC] DISABLE_G92_PERSISTENCE and clears restored #5210-#5219.
INI_FILE_NAME="$disable_g92_persistence_ini_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$disable_g92_startup_var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_g92_disable_persistence_startup.ngc >"$output_dir/cnc_sim_linuxcnc_g92_disable_persistence_startup.json"
disable_g92_stop_var_file="$build_dir/rs274ngc-disable-g92-stop.var"
: > "$disable_g92_stop_var_file"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_stop()
# clears #5210-#5219 on M2/M30 when DISABLE_G92_PERSISTENCE is enabled.
INI_FILE_NAME="$disable_g92_persistence_ini_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$disable_g92_stop_var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_g92_disable_persistence_set.ngc >"$output_dir/cnc_sim_linuxcnc_g92_disable_persistence_set.json"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$disable_g92_stop_var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_g92_disable_persistence_check.ngc >"$output_dir/cnc_sim_linuxcnc_g92_disable_persistence_check.json"
rotary_persistent_var_file="$build_dir/rs274ngc-rotary-persistent.var"
: > "$rotary_persistent_var_file"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$rotary_persistent_var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_rotary_persistent_parameters_set.ngc >"$output_dir/cnc_sim_linuxcnc_rotary_persistent_parameters_set.json"
@@ -1036,6 +1123,7 @@ nurbs_error_cases=(
"linuxcnc_g52_missing_subsequent_weight_error|Must specify positive weight P for every Control Point"
"linuxcnc_g52_noninteger_l_error|Non integer value for integer"
"linuxcnc_g52_zero_feed_error|Cannot make a NURBS with 0 feedrate"
"linuxcnc_g53_non_g52_motion_error|Between G5.2 and G5.3 codes, only additional G5.2 codes are allowed."
"linuxcnc_g53_without_g52_error|Cannot use G5.3 without G5.2 first"
"linuxcnc_g53_short_control_points_error|You must specify a number of control points at least equal to the order L = 3"
"linuxcnc_g53_after_intervening_motion_error|Cannot use G5.3 without G5.2 first"
@@ -1058,6 +1146,9 @@ for case in "${nurbs_error_cases[@]}"; do
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_setup()
# stores G10 L2 U/V/W through PROGRAM_TO_USER_LEN, and
# convert_coordinate_system() emits them through SET_G5X_OFFSET.
cp "$base_var_file" "$var_file"
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_offsets.ngc >"$output_dir/cnc_sim_linuxcnc_coordinate_offsets.json"
cp "$base_var_file" "$var_file"
@@ -1088,6 +1179,14 @@ CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linux
# src/emc/rs274ngc/interp_read.cc returns INTERP_ENDFILE at the matching '%',
# so later lines are ignored.
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_percent_file_ignores_trailing.ngc >"$output_dir/cnc_sim_linuxcnc_percent_file_ignores_trailing.json"
# Source basis: LinuxCNC unit_tests/interp/test_interp_basics.cc Convert G20 / G21
# asserts that unit changes rescale current linear axes and work/G92 linear
# offsets while rotary axes remain angular values.
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_units_current_position.ngc >"$output_dir/cnc_sim_linuxcnc_units_current_position.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_axis_offsets()
# stores G92 offsets through PROGRAM_TO_USER_LEN and restores them through
# USER_TO_PROGRAM_LEN after convert_length_units() changes active program units.
CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_units_g92_restore.ngc >"$output_dir/cnc_sim_linuxcnc_units_g92_restore.json"
CNC_SIM_RS274_NATIVE_OUTPUT_DIR="$output_dir" python3 - <<'PY'
import json
import os
@@ -1113,6 +1212,56 @@ required_motion = {"set-units", "set-plane", "set-spindle", "rapid", "set-feed",
check(OUTPUT_DIR / "cnc_sim_linuxcnc_basic_motion.json", required_motion)
check(OUTPUT_DIR / "cnc_sim_linuxcnc_basic_mill.json", required_motion | {"tool-change"})
units_current_position = load("cnc_sim_linuxcnc_units_current_position.json")
units_rapid = [
event
for event in units_current_position
if event["type"] == "rapid"
]
if len(units_rapid) != 3:
raise SystemExit(f"unexpected unit-current-position rapid count: {len(units_rapid)}")
if units_rapid[1]["start"] != units_rapid[1]["end"]:
raise SystemExit(
"LinuxCNC G21 current-position rescale was not reflected before the metric no-op rapid: "
f"{units_rapid[1]!r}"
)
if units_rapid[2]["start"] != units_rapid[2]["end"]:
raise SystemExit(
"LinuxCNC G20 current-position rescale was not reflected before the inch no-op rapid: "
f"{units_rapid[2]!r}"
)
if (units_rapid[1]["end"]["a"], units_rapid[1]["end"]["b"], units_rapid[1]["end"]["c"]) != (4, 5, 6):
raise SystemExit("LinuxCNC G21 unit change unexpectedly scaled rotary axes")
if (units_rapid[1]["end"]["u"], units_rapid[1]["end"]["v"], units_rapid[1]["end"]["w"]) != (177.8, 203.2, 228.6):
raise SystemExit("LinuxCNC G21 unit change did not scale U/V/W linear axes")
if (units_rapid[2]["end"]["u"], units_rapid[2]["end"]["v"], units_rapid[2]["end"]["w"]) != (7, 8, 9):
raise SystemExit("LinuxCNC G20 unit change did not restore U/V/W linear axes")
units_g92_restore = load("cnc_sim_linuxcnc_units_g92_restore.json")
units_g92_states = [
(
event["line"],
event["start"]["x"],
event["start"]["y"],
event["start"]["z"],
event["start"]["a"],
event["start"]["b"],
event["start"]["c"],
event["start"]["u"],
event["start"]["v"],
event["start"]["w"],
)
for event in units_g92_restore
if event["type"] == "set-g92-offset" and event["line"] > 0
]
expected_units_g92_states = [
(3, -1, -2, -3, -4, -5, -6, -7, -8, -9),
(5, 0, 0, 0, 0, 0, 0, 0, 0, 0),
(6, -25.4, -50.8, -76.2, -4, -5, -6, -177.8, -203.2, -228.6),
]
if units_g92_states != expected_units_g92_states:
raise SystemExit(f"unexpected LinuxCNC G20/G21 G92 restore states: {units_g92_states!r}")
fractional_linenumbers = load("cnc_sim_linuxcnc_fractional_linenumbers.json")
fractional_spindle_states = [
(event["line"], event["spindle"])
@@ -1518,6 +1667,36 @@ for expected in [(2, 611, 0), (3, 612, 0), (4, 640, 0.05)]:
if expected not in motion_mode_states:
raise SystemExit(f"missing motion mode state {expected!r}")
control_mode_tolerances = load("cnc_sim_linuxcnc_control_mode_tolerances.json")
control_mode_tolerance_states = [
(event["line"], event["reserved"], event["feed"])
for event in control_mode_tolerances
if event["type"] == "comment" and event["reserved"] in {611, 612, 640, 641}
]
expected_control_mode_tolerance_states = [
(2, 640, 0.05),
(2, 641, 0.02),
(3, 640, 0.03),
(3, 641, 0.03),
(4, 611, 0),
(5, 612, 0),
]
if control_mode_tolerance_states != expected_control_mode_tolerance_states:
raise SystemExit(f"unexpected G64 P/Q control mode tolerance states: {control_mode_tolerance_states!r}")
g64_default_tolerances = load("cnc_sim_linuxcnc_g64_default_tolerances.json")
g64_default_tolerance_states = [
(event["line"], event["reserved"], event["feed"])
for event in g64_default_tolerances
if event["type"] == "comment" and event["reserved"] in {640, 641}
]
expected_g64_default_tolerance_states = [
(3, 640, 0.07),
(3, 641, 0.04),
]
if g64_default_tolerance_states != expected_g64_default_tolerance_states:
raise SystemExit(f"unexpected INI-backed G64 default tolerance states: {g64_default_tolerance_states!r}")
tool_length = load("cnc_sim_linuxcnc_tool_length.json")
if not any(
event["type"] == "comment" and
@@ -1965,6 +2144,15 @@ if not any(
):
raise SystemExit("missing LinuxCNC small imperial center-format arc within tolerance")
arc_center_tolerance_tight_metric_default = load("cnc_sim_linuxcnc_arc_center_tolerance_tight_metric_default.json")
if not any(
event["type"] == "arc-feed" and
event["line"] == 5 and
event["end"]["x"] == 2
for event in arc_center_tolerance_tight_metric_default
):
raise SystemExit("missing LinuxCNC metric center-format arc under default tolerance")
incremental_r_arc = load("cnc_sim_linuxcnc_incremental_and_r_arc.json")
if not any(
event["type"] == "linear-feed" and
@@ -2437,6 +2625,23 @@ expected_readonly_moves = [
if readonly_moves != expected_readonly_moves:
raise SystemExit(f"unexpected LinuxCNC readonly named parameter motions: {readonly_moves!r}")
readonly_current = load("cnc_sim_linuxcnc_readonly_named_current_position.json")
if not any(
event["type"] == "linear-feed" and
event["line"] == 6 and
event["end"]["x"] == 10 and
event["end"]["y"] == 2 and
event["end"]["z"] == 3 and
event["end"]["a"] == 4 and
event["end"]["b"] == 5 and
event["end"]["c"] == 6 and
event["end"]["u"] == 7 and
event["end"]["v"] == 8 and
event["end"]["w"] == 9
for event in readonly_current
):
raise SystemExit("missing LinuxCNC readonly named current-position branch motion")
numbered_parameters = load("cnc_sim_linuxcnc_numbered_parameters.json")
if not any(
event["type"] == "linear-feed" and
@@ -2681,6 +2886,23 @@ if not any(
):
raise SystemExit("missing LinuxCNC persistent parameter reload branch motion")
disable_g92_startup = load("cnc_sim_linuxcnc_g92_disable_persistence_startup.json")
if not any(
event["type"] == "linear-feed" and
event["line"] == 5 and
event["end"]["x"] == 2
for event in disable_g92_startup
):
raise SystemExit("missing LinuxCNC DISABLE_G92_PERSISTENCE startup clear branch motion")
disable_g92_check = load("cnc_sim_linuxcnc_g92_disable_persistence_check.json")
if not any(
event["type"] == "linear-feed" and
event["line"] == 5 and
event["end"]["x"] == 1
for event in disable_g92_check
):
raise SystemExit("missing LinuxCNC DISABLE_G92_PERSISTENCE program-end clear branch motion")
rotary_persistent_parameters_check = load("cnc_sim_linuxcnc_rotary_persistent_parameters_check.json")
if not any(
event["type"] == "linear-feed" and
@@ -2744,7 +2966,10 @@ if not any(
event["tool"] == 1 and
event["start"]["x"] == 12.5 and
event["start"]["y"] == -3 and
event["start"]["z"] == 4
event["start"]["z"] == 4 and
event["start"]["u"] == 7 and
event["start"]["v"] == 8 and
event["start"]["w"] == 9
for event in g5x
):
raise SystemExit("missing G10 L2 G5X offset event")
@@ -2764,29 +2989,35 @@ if not any(event["type"] == "program-end" and event["line"] == 5 for event in co
l20 = load("cnc_sim_linuxcnc_coordinate_l20.json")
if not any(
event["type"] == "set-g5x-offset" and
event["line"] == 4 and
event["line"] == 5 and
event["tool"] == 2 and
event["start"]["x"] == 4 and
event["start"]["y"] == 4 and
event["start"]["z"] == 4
event["start"]["z"] == 4 and
event["start"]["u"] == 4 and
event["start"]["v"] == 4 and
event["start"]["w"] == 4
for event in l20
):
raise SystemExit("missing G10 L20 P2 offset event after G55 selection")
if not any(event["type"] == "program-end" and event["line"] == 5 for event in l20):
if not any(event["type"] == "program-end" and event["line"] == 6 for event in l20):
raise SystemExit("missing G10 L20 program end line number")
p0 = load("cnc_sim_linuxcnc_coordinate_p0.json")
if not any(
event["type"] == "set-g5x-offset" and
event["line"] == 3 and
event["line"] == 4 and
event["tool"] == 2 and
event["start"]["x"] == 7 and
event["start"]["y"] == 8 and
event["start"]["z"] == 9
event["start"]["z"] == 9 and
event["start"]["u"] == 10 and
event["start"]["v"] == 11 and
event["start"]["w"] == 12
for event in p0
):
raise SystemExit("missing G10 L2 P0 active G55 offset event")
if not any(event["type"] == "program-end" and event["line"] == 4 for event in p0):
if not any(event["type"] == "program-end" and event["line"] == 5 for event in p0):
raise SystemExit("missing G10 P0 program end line number")
select_all = load("cnc_sim_linuxcnc_coordinate_select_all.json")
@@ -2829,20 +3060,28 @@ if not any(event["type"] == "program-end" and event["line"] == 7 for event in g9
g52_offset = load("cnc_sim_linuxcnc_g52_offset.json")
g52_states = [
(event["line"], event["start"]["x"], event["start"]["y"], event["start"]["z"])
(
event["line"],
event["start"]["x"],
event["start"]["y"],
event["start"]["z"],
event["start"]["u"],
event["start"]["v"],
event["start"]["w"],
)
for event in g52_offset
if event["type"] == "set-g92-offset" and event["line"] > 0
]
expected_g52_states = [
(2, 1, 2, 0),
(3, 1, 3, 0),
(4, 0, 0, 0),
(5, 1, 3, 0),
(6, 0, 0, 0),
(3, 1, 2, 0, 4, 5, 0),
(4, 1, 3, 0, 4, 6, 0),
(5, 0, 0, 0, 0, 0, 0),
(6, 1, 3, 0, 4, 6, 0),
(7, 0, 0, 0, 0, 0, 0),
]
if g52_states != expected_g52_states:
raise SystemExit(f"unexpected G52 shared offset sequence: {g52_states!r}")
if not any(event["type"] == "program-end" and event["line"] == 7 for event in g52_offset):
if not any(event["type"] == "program-end" and event["line"] == 8 for event in g52_offset):
raise SystemExit("missing G52 offset program end line number")
g52_xz_nurbs = load("cnc_sim_linuxcnc_g52_xz_nurbs.json")
@@ -2896,6 +3135,7 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_program_end_m99.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_coolant.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_feed_modes.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_motion_modes.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_g64_default_tolerances.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_tool_length.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_dynamic_tool_length.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_canned_cycle.json"
@@ -2905,6 +3145,10 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_extended_plane_cycle.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_arc_planes.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_arc_distance_modes.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_arc_center_tolerance_good_imperial.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_arc_center_tolerance_tight_metric_default.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_arc_center_tolerance_tight_metric_ini.err"
echo "dumped $output_dir/cnc_sim_linuxcnc_units_current_position.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_units_g92_restore.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_predefined_positions.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_machine_coordinates.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_cutter_comp.json"
@@ -2926,6 +3170,7 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_named_parameter_normalization.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_ini_named_parameter.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_readonly_named_parameters.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_readonly_named_parameters_extra.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_readonly_named_current_position.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_numbered_parameters.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_rotary_probe_numbered_parameters.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_multi_rotary_probe_numbered_parameters.json"
@@ -2948,6 +3193,9 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_rotary_tool_offset_numbered_parameters
echo "dumped $output_dir/cnc_sim_linuxcnc_current_position_numbered_parameters.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_persistent_parameters_set.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_persistent_parameters_check.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_g92_disable_persistence_startup.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_g92_disable_persistence_set.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_g92_disable_persistence_check.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_rotary_persistent_parameters_set.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_rotary_persistent_parameters_check.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_g54_persistent_parameters_set.json"

View File

@@ -36,6 +36,19 @@ fi
preflight_cache_dir="$build_dir/preflight-cache"
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh --cache-dir "$preflight_cache_dir" "$manifest"
./check-linuxcnc-rs274-source-map.sh
./check-linuxcnc-kinematics-source-map.sh
./check-linuxcnc-opfs-source-map.sh
./check-linuxcnc-switchkins-remap-source-map.sh
./check-linuxcnc-wasm-smoke-source-map.sh
./check-linuxcnc-build-source-map.sh
./check-linuxcnc-browser-app-source-map.sh
./check-linuxcnc-workstreams-source-map.sh
./check-linuxcnc-tooldata-source-map.sh
./check-linuxcnc-wasm-shims-source-map.sh
./check-linuxcnc-wasm-blockers-source-map.sh
CNC_SIM_PREFLIGHT_CACHE_DIR="$preflight_cache_dir" CNC_SIM_WASM_BLOCKERS_SELF_CHECKS=1 \
./test-linuxcnc-wasm-blockers.sh linuxcnc-rs274-wasm-source-files.txt
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-switchkins-remap-table-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-kinematics-source-files.txt
# The LinuxCNC source-linked dump path is not concurrency-safe under parallel execution.
exec 9>"${TMPDIR:-/tmp}/cnc_sim_rs274_source_link.lock"
@@ -162,6 +175,13 @@ make --output-sync=target -j"$build_jobs" -f "$source_link_makefile"
var_file="$build_dir/rs274ngc-source.var"
base_var_file="$build_dir/rs274ngc-source-base.var"
ini_named_parameter_file="$(pwd)/tests/gcode/linuxcnc_ini_named_parameter.ini"
disable_g92_persistence_ini_file="$(pwd)/tests/gcode/linuxcnc_disable_g92_persistence.ini"
disable_fanuc_style_sub_ini_file="$(pwd)/tests/gcode/linuxcnc_disable_fanuc_style_sub.ini"
tight_arc_center_tolerance_ini_file="$(pwd)/tests/gcode/linuxcnc_tight_arc_center_tolerance.ini"
g64_default_tolerances_ini_file="$(pwd)/tests/gcode/linuxcnc_g64_default_tolerances.ini"
multi_spindle_modes_file="$(pwd)/tests/gcode/linuxcnc_multi_spindle_modes.ini"
multi_spindle_override_file="$(pwd)/tests/gcode/linuxcnc_multi_spindle_override.ini"
multi_spindle_threading_file="$(pwd)/tests/gcode/linuxcnc_multi_spindle_threading.ini"
output_dir="$build_dir/outputs"
mkdir -p "$output_dir"
cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$base_var_file"
@@ -196,12 +216,50 @@ CNC_SIM_RS274_VAR="$var_file" \
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_spindle_direction.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_spindle_direction.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, interp_execute.cc passes S-word $
# through convert_speed(), and interp_convert.cc routes M3/M4/M5 with a $
# selector to the selected spindle.
INI_FILE_NAME="$multi_spindle_modes_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_direction.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_direction.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, interp_execute.cc expands S-word $-1
# over all configured spindles, and interp_convert.cc expands M3/M4/M5 $-1
# over all configured spindles.
INI_FILE_NAME="$multi_spindle_modes_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_all_direction.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_all_direction.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, and interp_convert.cc M3/M4 without a $
# selector start only the default spindle.
INI_FILE_NAME="$multi_spindle_modes_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_default_start.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_default_start.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, and interp_convert.cc M5 without a $
# selector stops every configured spindle.
INI_FILE_NAME="$multi_spindle_modes_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_default_stop.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_default_stop.json"
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_spindle_modes.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_spindle_modes.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, and interp_execute.cc assigns
# active_spindle from $ before G95/G96/G97 conversion.
INI_FILE_NAME="$multi_spindle_modes_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_modes.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_modes.json"
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_spindle_orient.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_spindle_orient.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, and interp_convert.cc routes M19 with a
# $ selector to the selected spindle for orient and wait Canon calls.
INI_FILE_NAME="$multi_spindle_modes_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_orient.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_orient.json"
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_io_controls.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_io_controls.json"
@@ -246,6 +304,18 @@ done
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_override_controls.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_override_controls.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, and interp_convert.cc M51 applies
# speed override changes only to the selected $ spindle when present.
INI_FILE_NAME="$multi_spindle_override_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_override.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_override.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, and interp_convert.cc M51 without a $
# selector applies speed override changes to every configured spindle.
INI_FILE_NAME="$multi_spindle_override_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_override_all.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_override_all.json"
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_modal_state.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_modal_state.json"
@@ -261,6 +331,18 @@ fi
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_threading_sync.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_threading_sync.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, and interp_convert.cc G33/G33.1
# assign active_spindle from the $ selector before START_SPEED_FEED_SYNCH().
INI_FILE_NAME="$multi_spindle_threading_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_threading.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_threading.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [TRAJ] SPINDLES from INI_FILE_NAME, and interp_convert.cc assigns
# active_spindle from the G76 $ selector before convert_threading_cycle().
INI_FILE_NAME="$multi_spindle_threading_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_spindle_g76.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_multi_spindle_g76.json"
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_threading_cycle.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_threading_cycle.json"
@@ -325,6 +407,18 @@ CNC_SIM_RS274_VAR="$var_file" \
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_motion_modes.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_motion_modes.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
# convert_control_mode() sends G64 P through SET_MOTION_CONTROL_MODE()
# and G64 Q through SET_NAIVECAM_TOLERANCE(), defaulting Q to P when Q is omitted.
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_control_mode_tolerances.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_control_mode_tolerances.json"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [RS274NGC] G64_DEFAULT_TOLERANCE/G64_DEFAULT_NAIVETOLERANCE, and
# interp_convert.cc convert_control_mode() uses them when G64 omits P/Q.
INI_FILE_NAME="$g64_default_tolerances_ini_file" CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g64_default_tolerances.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_g64_default_tolerances.json"
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_tool_length.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_tool_length.json"
@@ -355,6 +449,25 @@ done
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_cycles.cc
# convert_cycle() rejects inverse-time feed mode and active cutter
# compensation before expanding canned cycles through convert_cycle_xy()/yz()/zx().
if CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_inverse_time_error.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_inverse_time_error.json" \
2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_inverse_time_error.err"; then
echo "expected linuxcnc_canned_cycle_inverse_time_error.ngc to fail" >&2
exit 1
fi
grep -q "Cannot use inverse time feed with canned cycles" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_inverse_time_error.err"
if CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_cutter_comp_error.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_cutter_comp_error.json" \
2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_cutter_comp_error.err"; then
echo "expected linuxcnc_canned_cycle_cutter_comp_error.ngc to fail" >&2
exit 1
fi
grep -q "Cannot use canned cycles with cutter compensation on" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_cutter_comp_error.err"
if CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_l0_error.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_l0_error.json" \
@@ -774,6 +887,9 @@ CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_readonly_named_parameters_extra.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_readonly_named_current_position.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_readonly_named_current_position.json"
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_readonly_named_parameter_assign_error.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_readonly_named_parameter_assign_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_readonly_named_parameter_assign_error.err"; then
@@ -1049,10 +1165,14 @@ for case in "${threading_check_error_cases[@]}"; do
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_source_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
# convert_straight() and convert_threading_cycle() reject invalid
# spindle state/selector, rotary-axis motion, and G76 I/J/K geometry
# before issuing synchronized threading motion.
# convert_arc(), convert_straight(), and convert_threading_cycle() reject invalid
# feed/spindle state, spindle selectors, rotary-axis motion, and G76 I/J/K
# geometry before issuing arc, straight, or synchronized threading motion.
threading_convert_error_cases=(
"linuxcnc_arc_inverse_time_missing_f_error|F word missing with inverse time arc move"
"linuxcnc_g1_zero_feed_error|Cannot do g1 with zero feed rate"
"linuxcnc_g1_inverse_time_missing_f_error|F word missing with inverse time g1 move"
"linuxcnc_g95_zero_spindle_speed_error|Cannot feed with zero spindle speed in feed per rev mode"
"linuxcnc_g33_stopped_spindle_error|Spindle not turning in G33"
"linuxcnc_g33_1_stopped_spindle_error|Spindle not turning in G33.1"
"linuxcnc_g33_spindle_dollar_out_of_range_error|Invalid spindle (\$) number in G33 move"
@@ -1075,6 +1195,26 @@ for case in "${threading_convert_error_cases[@]}"; do
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_source_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_probe()
# rejects active cutter compensation, zero feed, feed-per-revolution mode, and
# degenerate probe moves before issuing STRAIGHT_PROBE().
probe_convert_error_cases=(
"linuxcnc_probe_cutter_comp_error|Cannot probe with cutter radius comp on"
"linuxcnc_probe_zero_feed_error|Cannot probe with zero feed rate"
"linuxcnc_probe_feed_per_rev_error|Cannot probe with feed per rev mode"
"linuxcnc_probe_start_too_close_error|Start point too close to probe point"
)
for case in "${probe_convert_error_cases[@]}"; do
fixture=${case%%|*}
expected=${case#*|}
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" "tests/gcode/$fixture.ngc" \
>"$output_dir/cnc_sim_linuxcnc_source_$fixture.json" 2>"$output_dir/cnc_sim_linuxcnc_source_$fixture.err"; then
echo "expected $fixture.ngc to fail" >&2
exit 1
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_source_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_read.cc
# read_items() only consumes a leading N word before item dispatch;
# read_one_item() then uses the default reader table from interp_array.cc
@@ -1227,6 +1367,25 @@ for case in "${oword_reader_error_cases[@]}"; do
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_source_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [RS274NGC] DISABLE_FANUC_STYLE_SUB, and interp_read.cc rejects
# Fanuc-style M98/M99 and bare O-word subroutine forms when enabled.
disable_fanuc_style_error_cases=(
"linuxcnc_disable_fanuc_style_m98_error|DISABLE_FANUC_STYLE_SUB set in INI file, but found m98"
"linuxcnc_disable_fanuc_style_m99_error|DISABLE_FANUC_STYLE_SUB set in INI file, but found m99"
"linuxcnc_disable_fanuc_style_bare_oword_error|DISABLE_FANUC_STYLE_SUB disabled in INI file, but found bare O-word"
)
for case in "${disable_fanuc_style_error_cases[@]}"; do
fixture=${case%%|*}
expected=${case#*|}
if INI_FILE_NAME="$disable_fanuc_style_sub_ini_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" "tests/gcode/$fixture.ngc" \
>"$output_dir/cnc_sim_linuxcnc_source_$fixture.json" 2>"$output_dir/cnc_sim_linuxcnc_source_$fixture.err"; then
echo "expected $fixture.ngc to fail" >&2
exit 1
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_source_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_o_word.cc
# convert_control_functions() rejects unmatched, duplicate, and
# out-of-context O-word control labels during execution.
@@ -1318,6 +1477,69 @@ if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
fi
grep -q "Cannot wait for digital input with cutter radius compensation on" \
"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_wait_input_error.err"
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_cutter_comp_tool_offset_error.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_tool_offset_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_tool_offset_error.err"; then
echo "expected linuxcnc_cutter_comp_tool_offset_error.ngc to fail" >&2
exit 1
fi
grep -q "Cannot change tool offset with cutter radius compensation on" \
"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_tool_offset_error.err"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
# convert_control_mode(), convert_coordinate_system(), and convert_home()
# reject control-mode changes, coordinate-system changes, and G28/G30 while
# cutter radius compensation is active.
cutter_comp_g_error_cases=(
"linuxcnc_cutter_comp_axis_offset_error|Cannot change axis offsets with cutter radius comp"
"linuxcnc_cutter_comp_control_mode_error|Cannot change control mode with cutter radius compensation on"
"linuxcnc_cutter_comp_coordinate_system_error|Cannot change coordinate systems with cutter radius compensation on"
"linuxcnc_cutter_comp_g53_error|Cannot use g53 with cutter radius comp"
"linuxcnc_cutter_comp_home_error|Cannot use g28 or g30 with cutter radius comp"
"linuxcnc_cutter_comp_plane_change_error|Cannot change planes with cutter radius compensation on"
)
for case in "${cutter_comp_g_error_cases[@]}"; do
fixture=${case%%|*}
expected=${case#*|}
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" "tests/gcode/$fixture.ngc" \
>"$output_dir/cnc_sim_linuxcnc_source_$fixture.json" 2>"$output_dir/cnc_sim_linuxcnc_source_$fixture.err"; then
echo "expected $fixture.ngc to fail" >&2
exit 1
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_source_$fixture.err"
done
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
# convert_m() rejects M63-M65/M67-M68/M48-M53 while cutter radius
# compensation is active before enqueueing digital/analog output or override
# canon side effects.
cutter_comp_m_error_cases=(
"linuxcnc_cutter_comp_m63_error|Cannot set motion digital output with cutter radius compensation on"
"linuxcnc_cutter_comp_m64_error|Cannot set auxiliary digital output with cutter radius compensation on"
"linuxcnc_cutter_comp_m65_error|Cannot set auxiliary digital output with cutter radius compensation on"
"linuxcnc_cutter_comp_m67_error|Cannot set motion analog output with cutter radius compensation on"
"linuxcnc_cutter_comp_m68_error|Cannot set auxiliary analog output with cutter radius compensation on"
"linuxcnc_cutter_comp_m48_error|Cannot enable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m49_error|Cannot disable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m50_enable_error|Cannot enable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m50_disable_error|Cannot disable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m51_enable_error|Cannot enable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m51_disable_error|Cannot disable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m52_enable_error|Cannot enable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m52_disable_error|Cannot disable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m53_enable_error|Cannot enable overrides with cutter radius compensation on"
"linuxcnc_cutter_comp_m53_disable_error|Cannot disable overrides with cutter radius compensation on"
)
for case in "${cutter_comp_m_error_cases[@]}"; do
fixture=${case%%|*}
expected=${case#*|}
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" "tests/gcode/$fixture.ngc" \
>"$output_dir/cnc_sim_linuxcnc_source_$fixture.json" 2>"$output_dir/cnc_sim_linuxcnc_source_$fixture.err"; then
echo "expected $fixture.ngc to fail" >&2
exit 1
fi
grep -Fq "$expected" "$output_dir/cnc_sim_linuxcnc_source_$fixture.err"
done
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_cutter_comp_g71_error.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_g71_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_g71_error.err"; then
@@ -1354,6 +1576,11 @@ if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
echo "expected linuxcnc_abort_hot_comment.ngc to fail" >&2
exit 1
fi
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc
# convert_comment() routes ABORT hot comments through convert_param_comment()
# and setSavedError(), preserving expanded numbered and named parameters.
grep -Fq "execute failed: MixedCase param42=20.000000 named=4711.000000" \
"$output_dir/cnc_sim_linuxcnc_source_abort_hot_comment.err"
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_ccomp_gouging_error.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_ccomp_gouging_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_ccomp_gouging_error.err"; then
@@ -1437,6 +1664,23 @@ CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_persistent_parameters_check.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_persistent_parameters_check.json"
disable_g92_startup_var_file="$build_dir/rs274ngc-source-disable-g92-startup.var"
printf '5210\t1.000000\n5211\t-4.000000\n5212\t-5.000000\n5213\t-6.000000\n5220\t1.000000\n' > "$disable_g92_startup_var_file"
# Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc init reads
# [RS274NGC] DISABLE_G92_PERSISTENCE and clears restored #5210-#5219.
INI_FILE_NAME="$disable_g92_persistence_ini_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$disable_g92_startup_var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g92_disable_persistence_startup.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_g92_disable_persistence_startup.json"
disable_g92_stop_var_file="$build_dir/rs274ngc-source-disable-g92-stop.var"
: > "$disable_g92_stop_var_file"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_stop()
# clears #5210-#5219 on M2/M30 when DISABLE_G92_PERSISTENCE is enabled.
INI_FILE_NAME="$disable_g92_persistence_ini_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$disable_g92_stop_var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g92_disable_persistence_set.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_g92_disable_persistence_set.json"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$disable_g92_stop_var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g92_disable_persistence_check.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_g92_disable_persistence_check.json"
rotary_persistent_var_file="$build_dir/rs274ngc-rotary-persistent.var"
: > "$rotary_persistent_var_file"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$rotary_persistent_var_file" \
@@ -1486,6 +1730,17 @@ CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_arc_center_tolerance_good_imperial.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_good_imperial.json"
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_arc_center_tolerance_tight_metric.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_tight_metric_default.json"
if INI_FILE_NAME="$tight_arc_center_tolerance_ini_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_arc_center_tolerance_tight_metric.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_tight_metric_ini.json" 2>"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_tight_metric_ini.err"; then
echo "expected linuxcnc_arc_center_tolerance_tight_metric.ngc to fail with tight INI tolerance" >&2
exit 1
fi
grep -Fq "Radius to end of arc differs from radius to start" \
"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_tight_metric_ini.err"
if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_arc_center_tolerance_bad_metric.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_bad_metric.json" 2>"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_bad_metric.err"; then
@@ -1524,6 +1779,7 @@ nurbs_error_cases=(
"linuxcnc_g52_missing_subsequent_weight_error|Must specify positive weight P for every Control Point"
"linuxcnc_g52_noninteger_l_error|Non integer value for integer"
"linuxcnc_g52_zero_feed_error|Cannot make a NURBS with 0 feedrate"
"linuxcnc_g53_non_g52_motion_error|Between G5.2 and G5.3 codes, only additional G5.2 codes are allowed."
"linuxcnc_g53_without_g52_error|Cannot use G5.3 without G5.2 first"
"linuxcnc_g53_short_control_points_error|You must specify a number of control points at least equal to the order L = 3"
"linuxcnc_g53_after_intervening_motion_error|Cannot use G5.3 without G5.2 first"
@@ -1549,6 +1805,9 @@ done
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g76only.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_g76only.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_setup()
# stores G10 L2 U/V/W through PROGRAM_TO_USER_LEN, and
# convert_coordinate_system() emits them through SET_G5X_OFFSET.
cp "$base_var_file" "$var_file"
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_coordinate_offsets.ngc \
@@ -1605,6 +1864,18 @@ CNC_SIM_RS274_VAR="$var_file" \
CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_percent_file_ignores_trailing.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_percent_file_ignores_trailing.json"
# Source basis: LinuxCNC unit_tests/interp/test_interp_basics.cc Convert G20 / G21
# asserts that unit changes rescale current linear axes while rotary axes remain
# angular values.
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_units_current_position.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_units_current_position.json"
# Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_axis_offsets()
# stores G92 offsets through PROGRAM_TO_USER_LEN and restores them through
# USER_TO_PROGRAM_LEN after convert_length_units() changes active program units.
CNC_SIM_RS274_VAR="$var_file" \
"$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_units_g92_restore.ngc \
>"$output_dir/cnc_sim_linuxcnc_source_units_g92_restore.json"
CNC_SIM_RS274_SOURCE_OUTPUT_DIR="$output_dir" python3 - <<'PY'
import json
@@ -1642,6 +1913,56 @@ required_motion = {
check_motion("cnc_sim_linuxcnc_source_basic_motion.json", required_motion)
check_motion("cnc_sim_linuxcnc_source_basic_mill.json", required_motion | {"tool-change"})
units_current_position = load("cnc_sim_linuxcnc_source_units_current_position.json")
units_rapid = [
event
for event in units_current_position
if event["type"] == "rapid"
]
if len(units_rapid) != 3:
raise SystemExit(f"unexpected source-linked unit-current-position rapid count: {len(units_rapid)}")
if units_rapid[1]["start"] != units_rapid[1]["end"]:
raise SystemExit(
"source-linked LinuxCNC G21 current-position rescale was not reflected before the metric no-op rapid: "
f"{units_rapid[1]!r}"
)
if units_rapid[2]["start"] != units_rapid[2]["end"]:
raise SystemExit(
"source-linked LinuxCNC G20 current-position rescale was not reflected before the inch no-op rapid: "
f"{units_rapid[2]!r}"
)
if (units_rapid[1]["end"]["a"], units_rapid[1]["end"]["b"], units_rapid[1]["end"]["c"]) != (4, 5, 6):
raise SystemExit("source-linked LinuxCNC G21 unit change unexpectedly scaled rotary axes")
if (units_rapid[1]["end"]["u"], units_rapid[1]["end"]["v"], units_rapid[1]["end"]["w"]) != (177.8, 203.2, 228.6):
raise SystemExit("source-linked LinuxCNC G21 unit change did not scale U/V/W linear axes")
if (units_rapid[2]["end"]["u"], units_rapid[2]["end"]["v"], units_rapid[2]["end"]["w"]) != (7, 8, 9):
raise SystemExit("source-linked LinuxCNC G20 unit change did not restore U/V/W linear axes")
units_g92_restore = load("cnc_sim_linuxcnc_source_units_g92_restore.json")
units_g92_states = [
(
event["line"],
event["start"]["x"],
event["start"]["y"],
event["start"]["z"],
event["start"]["a"],
event["start"]["b"],
event["start"]["c"],
event["start"]["u"],
event["start"]["v"],
event["start"]["w"],
)
for event in units_g92_restore
if event["type"] == "set-g92-offset" and event["line"] > 0
]
expected_units_g92_states = [
(3, -1, -2, -3, -4, -5, -6, -7, -8, -9),
(5, 0, 0, 0, 0, 0, 0, 0, 0, 0),
(6, -25.4, -50.8, -76.2, -4, -5, -6, -177.8, -203.2, -228.6),
]
if units_g92_states != expected_units_g92_states:
raise SystemExit(f"unexpected source-linked LinuxCNC G20/G21 G92 restore states: {units_g92_states!r}")
fractional_linenumbers = load("cnc_sim_linuxcnc_source_fractional_linenumbers.json")
fractional_spindle_states = [
(event["line"], event["spindle"])
@@ -1728,6 +2049,78 @@ if (3, 1200, 2) not in spindle_states:
if (4, 0, 0) not in spindle_states:
raise SystemExit("missing source-linked M5 stopped spindle state")
multi_spindle_direction = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_direction.json").read_text())
multi_spindle_direction_states = [
(event["line"], event["tool"], event["spindle"], event["reserved"])
for event in multi_spindle_direction
if event["type"] == "set-spindle" and event["line"] in {5, 6, 7, 8}
]
expected_multi_spindle_direction_states = [
(5, 1, 1200, 0),
(6, 1, 1200, 1),
(7, 1, 1200, 2),
(8, 1, 0, 0),
]
if multi_spindle_direction_states != expected_multi_spindle_direction_states:
raise SystemExit(
"unexpected source-linked selected-spindle M3/M4/M5 states: "
f"{multi_spindle_direction_states!r}"
)
multi_spindle_all_direction = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_all_direction.json").read_text())
multi_spindle_all_direction_states = [
(event["line"], event["tool"], event["spindle"], event["reserved"])
for event in multi_spindle_all_direction
if event["type"] == "set-spindle" and event["line"] in {5, 6, 7, 8}
]
expected_multi_spindle_all_direction_states = [
(5, 0, 1200, 0),
(5, 1, 1200, 0),
(6, 0, 1200, 1),
(6, 1, 1200, 1),
(7, 0, 1200, 2),
(7, 1, 1200, 2),
(8, 0, 0, 0),
(8, 1, 0, 0),
]
if multi_spindle_all_direction_states != expected_multi_spindle_all_direction_states:
raise SystemExit(
"unexpected source-linked all-spindles S/M3/M4/M5 states: "
f"{multi_spindle_all_direction_states!r}"
)
multi_spindle_default_start = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_default_start.json").read_text())
multi_spindle_default_start_states = [
(event["line"], event["tool"], event["spindle"], event["reserved"])
for event in multi_spindle_default_start
if event["type"] == "set-spindle" and event["line"] in {5, 6}
]
expected_multi_spindle_default_start_states = [
(5, 0, 1200, 1),
(6, 0, 1200, 2),
]
if multi_spindle_default_start_states != expected_multi_spindle_default_start_states:
raise SystemExit(
"unexpected source-linked default M3/M4 spindle states: "
f"{multi_spindle_default_start_states!r}"
)
multi_spindle_default_stop = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_default_stop.json").read_text())
multi_spindle_default_stop_states = [
(event["line"], event["tool"], event["spindle"], event["reserved"])
for event in multi_spindle_default_stop
if event["type"] == "set-spindle" and event["line"] == 6
]
expected_multi_spindle_default_stop_states = [
(6, 0, 0, 0),
(6, 1, 0, 0),
]
if multi_spindle_default_stop_states != expected_multi_spindle_default_stop_states:
raise SystemExit(
"unexpected source-linked default M5 all-spindles stop states: "
f"{multi_spindle_default_stop_states!r}"
)
spindle_modes = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_spindle_modes.json").read_text())
spindle_mode_states = [
(event["line"], event["reserved"], event["feed"], event["tool"])
@@ -1742,6 +2135,27 @@ expected_spindle_mode_states = [
if spindle_mode_states != expected_spindle_mode_states:
raise SystemExit(f"unexpected source-linked G96/G97 spindle mode states: {spindle_mode_states!r}")
multi_spindle_modes = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_modes.json").read_text())
multi_spindle_mode_states = [
(event["line"], event["reserved"], event["feed"], event["tool"])
for event in multi_spindle_modes
if event["type"] == "comment" and event["line"] in {4, 5} and event["reserved"] in {960, 970}
]
expected_multi_spindle_mode_states = [
(4, 970, 0, 1),
(5, 960, 2500, 1),
]
if multi_spindle_mode_states != expected_multi_spindle_mode_states:
raise SystemExit(f"unexpected source-linked selected-spindle G96/G97 states: {multi_spindle_mode_states!r}")
if not any(
event["type"] == "set-feed" and
event["line"] == 6 and
event["reserved"] == 1 and
event["tool"] == 1
for event in multi_spindle_modes
):
raise SystemExit("missing source-linked selected-spindle G95 feed mode event")
spindle_orient = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_spindle_orient.json").read_text())
if not any(
event["type"] == "comment" and
@@ -1763,6 +2177,27 @@ if not any(
):
raise SystemExit("missing source-linked M19 spindle orient wait state")
multi_spindle_orient = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_orient.json").read_text())
if not any(
event["type"] == "comment" and
event["line"] == 4 and
event["reserved"] == 1901 and
event["tool"] == 1 and
event["feed"] == 45 and
event["arcTurns"] == 1
for event in multi_spindle_orient
):
raise SystemExit("missing source-linked selected-spindle M19 orient state")
if not any(
event["type"] == "comment" and
event["line"] == 4 and
event["reserved"] == 1902 and
event["tool"] == 1 and
event["dwellSeconds"] == 2
for event in multi_spindle_orient
):
raise SystemExit("missing source-linked selected-spindle M19 orient wait state")
io_controls = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_io_controls.json").read_text())
io_states = [
(event["line"], event["reserved"], event["tool"], event["feed"], event["arcTurns"], event["dwellSeconds"])
@@ -1806,6 +2241,70 @@ expected_override_states = [
if override_states != expected_override_states:
raise SystemExit(f"unexpected source-linked M48-M53 override states: {override_states!r}")
multi_spindle_override = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_override.json").read_text())
multi_spindle_override_states = [
(event["line"], event["reserved"], event["tool"], event["feed"])
for event in multi_spindle_override
if event["type"] == "comment" and event["reserved"] == 51
]
expected_multi_spindle_override_states = [
(4, 51, 1, 0),
(5, 51, 1, 1),
]
if multi_spindle_override_states != expected_multi_spindle_override_states:
raise SystemExit(
"unexpected source-linked M51 selected-spindle override states: "
f"{multi_spindle_override_states!r}"
)
multi_spindle_override_all = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_override_all.json").read_text())
multi_spindle_override_all_states = [
(event["line"], event["reserved"], event["tool"], event["feed"])
for event in multi_spindle_override_all
if event["type"] == "comment" and event["reserved"] == 51
]
expected_multi_spindle_override_all_states = [
(4, 51, 0, 0),
(4, 51, 1, 0),
(5, 51, 0, 1),
(5, 51, 1, 1),
]
if multi_spindle_override_all_states != expected_multi_spindle_override_all_states:
raise SystemExit(
"unexpected source-linked M51 all-spindles override states: "
f"{multi_spindle_override_all_states!r}"
)
control_mode_tolerances = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_control_mode_tolerances.json").read_text())
control_mode_tolerance_states = [
(event["line"], event["reserved"], event["feed"])
for event in control_mode_tolerances
if event["type"] == "comment" and event["reserved"] in {611, 612, 640, 641}
]
expected_control_mode_tolerance_states = [
(2, 640, 0.05),
(2, 641, 0.02),
(3, 640, 0.03),
(3, 641, 0.03),
(4, 611, 0),
(5, 612, 0),
]
if control_mode_tolerance_states != expected_control_mode_tolerance_states:
raise SystemExit(f"unexpected source-linked G64 P/Q control mode tolerance states: {control_mode_tolerance_states!r}")
g64_default_tolerances = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g64_default_tolerances.json").read_text())
g64_default_tolerance_states = [
(event["line"], event["reserved"], event["feed"])
for event in g64_default_tolerances
if event["type"] == "comment" and event["reserved"] in {640, 641}
]
expected_g64_default_tolerance_states = [
(3, 640, 0.07),
(3, 641, 0.04),
]
if g64_default_tolerance_states != expected_g64_default_tolerance_states:
raise SystemExit(f"unexpected source-linked INI-backed G64 default tolerance states: {g64_default_tolerance_states!r}")
modal_state = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_modal_state.json").read_text())
if not any(
event["type"] == "rapid" and
@@ -1863,6 +2362,50 @@ if not any(
):
raise SystemExit("missing source-linked G33.1 rigid tap motion line")
multi_spindle_threading = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_threading.json").read_text())
multi_spindle_sync_states = [
(event["line"], event["reserved"], event["feed"], event["tool"])
for event in multi_spindle_threading
if event["type"] == "comment" and event["reserved"] in {3300, 3301, 3302}
]
expected_multi_spindle_sync_states = [
(6, 3301, 1, 1),
(6, 3300, 0, 0),
(8, 3301, 1, 1),
(8, 3300, 0, 0),
]
if multi_spindle_sync_states != expected_multi_spindle_sync_states:
raise SystemExit(
"unexpected source-linked G33/G33.1 selected-spindle sync states: "
f"{multi_spindle_sync_states!r}"
)
if not any(
event["type"] == "linear-feed" and event["line"] == 8 and event["end"]["z"] == 0
for event in multi_spindle_threading
):
raise SystemExit("missing source-linked G33.1 selected-spindle rigid tap motion line")
multi_spindle_g76 = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_spindle_g76.json").read_text())
if not any(
event["type"] == "comment" and
event["line"] == 6 and
event["reserved"] == 3301 and
event["tool"] == 1 and
event["feed"] == 0.05
for event in multi_spindle_g76
):
raise SystemExit("missing source-linked G76 selected-spindle sync start")
if not any(
event["type"] == "comment" and event["line"] == 6 and event["reserved"] == 3300
for event in multi_spindle_g76
):
raise SystemExit("missing source-linked G76 selected-spindle sync stop")
if not any(
event["type"] == "linear-feed" and event["line"] == 6 and event["end"]["z"] == -1
for event in multi_spindle_g76
):
raise SystemExit("missing source-linked G76 selected-spindle final threading pass")
threading_cycle = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_threading_cycle.json").read_text())
if not any(
event["type"] == "comment" and
@@ -2575,8 +3118,32 @@ if actual_cutter_comp != expected_cutter_comp:
raise SystemExit(f"unexpected source-linked cutter compensation path: {actual_cutter_comp!r}")
comments = load("cnc_sim_linuxcnc_source_comments.json")
if not any(event["type"] == "comment" and event["line"] == 2 for event in comments):
raise SystemExit("missing source-linked LinuxCNC comment line event")
# Source basis: LinuxCNC src/emc/rs274ngc/interp_read.cc read_comment()
# stores parenthesized comments for convert_comment(), while read_semicolon()
# skips ordinary semicolon comments to end-of-line unless the line starts
# with ;py,. src/emc/rs274ngc/interp_convert.cc convert_comment() emits
# ordinary parenthesized comments through enqueue_COMMENT().
comment_events = [
(index, event["line"])
for index, event in enumerate(comments)
if event["type"] == "comment" and event["reserved"] == 0
]
comment_lines = [line for _, line in comment_events]
for expected_line in (2, 4):
if expected_line not in comment_lines:
raise SystemExit(f"missing source-linked LinuxCNC comment line event {expected_line}")
for unexpected_line in (5, 6):
if unexpected_line in comment_lines:
raise SystemExit(f"unexpected source-linked ordinary semicolon comment event {unexpected_line}")
motion_line4_index = next(
index for index, event in enumerate(comments)
if event["type"] == "rapid" and event["line"] == 4 and event["end"]["x"] == 1
)
comment_line4_index = next(index for index, line in comment_events if line == 4)
if comment_line4_index >= motion_line4_index:
raise SystemExit("source-linked parenthesized inline comment was not emitted before same-line motion")
if not any(event["type"] == "rapid" and event["line"] == 5 and event["end"]["x"] == 2 for event in comments):
raise SystemExit("source-linked semicolon-commented motion line did not execute before line-end comment")
probe = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_probe_no_error.json").read_text())
probe_events = [
@@ -2817,6 +3384,23 @@ expected_readonly_common_moves = [
if readonly_common_moves != expected_readonly_common_moves:
raise SystemExit(f"unexpected source-linked readonly named parameter motions: {readonly_common_moves!r}")
readonly_current = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_readonly_named_current_position.json").read_text())
if not any(
event["type"] == "linear-feed" and
event["line"] == 6 and
event["end"]["x"] == 10 and
event["end"]["y"] == 2 and
event["end"]["z"] == 3 and
event["end"]["a"] == 4 and
event["end"]["b"] == 5 and
event["end"]["c"] == 6 and
event["end"]["u"] == 7 and
event["end"]["v"] == 8 and
event["end"]["w"] == 9
for event in readonly_current
):
raise SystemExit("missing source-linked readonly named current-position branch motion")
readonly_named = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json").read_text())
if not any(
event["type"] == "linear-feed" and
@@ -3063,6 +3647,15 @@ if not any(
):
raise SystemExit("missing source-linked small imperial center-format arc within tolerance")
arc_center_tolerance_tight_metric_default = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_arc_center_tolerance_tight_metric_default.json").read_text())
if not any(
event["type"] == "arc-feed" and
event["line"] == 5 and
event["end"]["x"] == 2
for event in arc_center_tolerance_tight_metric_default
):
raise SystemExit("missing source-linked metric center-format arc under default tolerance")
g76only = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g76only.json").read_text())
if not any(
event["type"] == "comment" and
@@ -3322,6 +3915,23 @@ if not any(
):
raise SystemExit("missing source-linked persistent parameter reload branch motion")
disable_g92_startup = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g92_disable_persistence_startup.json").read_text())
if not any(
event["type"] == "linear-feed" and
event["line"] == 5 and
event["end"]["x"] == 2
for event in disable_g92_startup
):
raise SystemExit("missing source-linked DISABLE_G92_PERSISTENCE startup clear branch motion")
disable_g92_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g92_disable_persistence_check.json").read_text())
if not any(
event["type"] == "linear-feed" and
event["line"] == 5 and
event["end"]["x"] == 1
for event in disable_g92_check
):
raise SystemExit("missing source-linked DISABLE_G92_PERSISTENCE program-end clear branch motion")
rotary_persistent_parameters_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_rotary_persistent_parameters_check.json").read_text())
if not any(
event["type"] == "linear-feed" and
@@ -3385,7 +3995,10 @@ if not any(
event["tool"] == 1 and
event["start"]["x"] == 12.5 and
event["start"]["y"] == -3 and
event["start"]["z"] == 4
event["start"]["z"] == 4 and
event["start"]["u"] == 7 and
event["start"]["v"] == 8 and
event["start"]["w"] == 9
for event in g5x
):
raise SystemExit("missing source-linked G10 L2 G5X offset event")
@@ -3405,29 +4018,35 @@ if not any(event["type"] == "program-end" and event["line"] == 5 for event in co
l20 = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_coordinate_l20.json").read_text())
if not any(
event["type"] == "set-g5x-offset" and
event["line"] == 4 and
event["line"] == 5 and
event["tool"] == 2 and
event["start"]["x"] == 4 and
event["start"]["y"] == 4 and
event["start"]["z"] == 4
event["start"]["z"] == 4 and
event["start"]["u"] == 4 and
event["start"]["v"] == 4 and
event["start"]["w"] == 4
for event in l20
):
raise SystemExit("missing source-linked G10 L20 P2 offset event after G55 selection")
if not any(event["type"] == "program-end" and event["line"] == 5 for event in l20):
if not any(event["type"] == "program-end" and event["line"] == 6 for event in l20):
raise SystemExit("missing source-linked G10 L20 program end line number")
p0 = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_coordinate_p0.json").read_text())
if not any(
event["type"] == "set-g5x-offset" and
event["line"] == 3 and
event["line"] == 4 and
event["tool"] == 2 and
event["start"]["x"] == 7 and
event["start"]["y"] == 8 and
event["start"]["z"] == 9
event["start"]["z"] == 9 and
event["start"]["u"] == 10 and
event["start"]["v"] == 11 and
event["start"]["w"] == 12
for event in p0
):
raise SystemExit("missing source-linked G10 L2 P0 active G55 offset event")
if not any(event["type"] == "program-end" and event["line"] == 4 for event in p0):
if not any(event["type"] == "program-end" and event["line"] == 5 for event in p0):
raise SystemExit("missing source-linked G10 P0 program end line number")
select_all = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_coordinate_select_all.json").read_text())
@@ -3470,20 +4089,28 @@ if not any(event["type"] == "program-end" and event["line"] == 7 for event in g9
g52_offset = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g52_offset.json").read_text())
g52_states = [
(event["line"], event["start"]["x"], event["start"]["y"], event["start"]["z"])
(
event["line"],
event["start"]["x"],
event["start"]["y"],
event["start"]["z"],
event["start"]["u"],
event["start"]["v"],
event["start"]["w"],
)
for event in g52_offset
if event["type"] == "set-g92-offset" and event["line"] > 0
]
expected_g52_states = [
(2, 1, 2, 0),
(3, 1, 3, 0),
(4, 0, 0, 0),
(5, 1, 3, 0),
(6, 0, 0, 0),
(3, 1, 2, 0, 4, 5, 0),
(4, 1, 3, 0, 4, 6, 0),
(5, 0, 0, 0, 0, 0, 0),
(6, 1, 3, 0, 4, 6, 0),
(7, 0, 0, 0, 0, 0, 0),
]
if g52_states != expected_g52_states:
raise SystemExit(f"unexpected source-linked G52 shared offset sequence: {g52_states!r}")
if not any(event["type"] == "program-end" and event["line"] == 7 for event in g52_offset):
if not any(event["type"] == "program-end" and event["line"] == 8 for event in g52_offset):
raise SystemExit("missing source-linked G52 offset program end line number")
g52_xz_nurbs = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g52_xz_nurbs.json").read_text())
@@ -3517,14 +4144,21 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_source_basic_mill.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_incremental_and_r_arc.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_rtcp_controls.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_spindle_direction.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_multi_spindle_direction.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_multi_spindle_all_direction.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_multi_spindle_default_start.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_multi_spindle_default_stop.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_spindle_modes.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_spindle_orient.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_multi_spindle_orient.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_io_controls.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_override_controls.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_multi_spindle_override_all.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_modal_state.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_modal_autorestore.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_threading_sync.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_threading_cycle.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_multi_spindle_g76.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_lathe_diameter_mode.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_lathe_diameter_g76.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_tool_number.json"
@@ -3534,6 +4168,7 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_source_program_end_m99.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_coolant.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_feed_modes.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_motion_modes.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_g64_default_tolerances.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_tool_length.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_dynamic_tool_length.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_canned_cycle.json"
@@ -3557,6 +4192,7 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_source_named_parameter_normalization.j
echo "dumped $output_dir/cnc_sim_linuxcnc_source_ini_named_parameter.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_readonly_named_parameters.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_readonly_named_current_position.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_value_returned.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_call_level.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_exists_word_value.json"
@@ -3565,6 +4201,8 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_source_parameter_expression_assignment
echo "dumped $output_dir/cnc_sim_linuxcnc_source_word_bracket_whitespace.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_m98_mixed_styles_variables.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_good_imperial.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_tight_metric_default.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_tight_metric_ini.err"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_g76only.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_numbered_parameters.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_probe_numbered_parameters.json"
@@ -3588,6 +4226,9 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_tool_offset_numbered_par
echo "dumped $output_dir/cnc_sim_linuxcnc_source_current_position_numbered_parameters.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_persistent_parameters_set.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_persistent_parameters_check.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_g92_disable_persistence_startup.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_g92_disable_persistence_set.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_g92_disable_persistence_check.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_persistent_parameters_set.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_persistent_parameters_check.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_g54_persistent_parameters_set.json"
@@ -3610,3 +4251,5 @@ echo "dumped $output_dir/cnc_sim_linuxcnc_source_fractional_linenumbers.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_extended_linenumbers.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_close_and_downcase_words.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_percent_file_ignores_trailing.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_units_current_position.json"
echo "dumped $output_dir/cnc_sim_linuxcnc_source_units_g92_restore.json"

View File

@@ -1531,6 +1531,10 @@ grep -F 'LinuxCNC source basis: src/emc/rs274ngc/interp_base.cc native loader fl
grep -F 'LinuxCNC source basis: src/emc/rs274ngc/gcodemodule.cc defines _task=0' core/wasm_shims/linuxcnc_runtime_shim.cc >/dev/null
grep -F 'LinuxCNC source basis: src/emc/task/taskclass.cc and' core/wasm_shims/linuxcnc_runtime_shim.cc >/dev/null
grep -F 'LinuxCNC source basis: src/emc/rs274ngc/interp_namedparams.cc fetch_hal_param()' core/wasm_shims/linuxcnc_runtime_shim.cc >/dev/null
grep -F 'src/emc/rs274ngc/interp_namedparams.cc calls the HAL lookup functions' core/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc >/dev/null
grep -F 'bool hal_lookup_is_unavailable()' core/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc >/dev/null
grep -F 'hal_get_pin_value_by_name("cnc-sim.missing-pin", &type, &data, &connected) != -1' core/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc >/dev/null
grep -F 'type != HAL_TYPE_UNINITIALIZED' core/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc >/dev/null
grep -F 'LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc uses wordexp()' core/wasm_shims/linuxcnc_runtime_shim.cc >/dev/null
grep -F 'extern "C" int wordexp(const char *words, wordexp_t *pwordexp, int)' core/wasm_shims/linuxcnc_runtime_shim.cc >/dev/null
grep -F 'extern "C" void wordfree(wordexp_t *pwordexp)' core/wasm_shims/linuxcnc_runtime_shim.cc >/dev/null

View File

@@ -7,6 +7,10 @@ cd "$(dirname "$0")"
# whose runtime dynamic loading edge is satisfied by the wasm-safe dlfcn shim.
probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode interp-base-link)
"$probe"
"$probe" 2>"$probe.err"
grep -F "emcTaskInit: could not open interpreter '/tmp/cnc_sim_missing_absolute_interpreter.so': dynamic loading is unavailable in wasm-safe probe" \
"$probe.err" >/dev/null
grep -F "emcTaskInit: could not open interpreter '" "$probe.err" | \
grep -F "/lib/linuxcnc/nonexistent-interpreter.so': dynamic loading is unavailable in wasm-safe probe" >/dev/null
echo "linuxcnc wasm interp_base link probe passed"

View File

@@ -4,9 +4,9 @@ set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this probe derives required Python C API exports from
# RS274 Python/remap sources in src/emc/rs274ngc, including
# interp_namedparams.cc, interp_o_word.cc, interp_python.cc, interp_remap.cc,
# rs274ngc_pre.cc, and the py*/module binding sources.
# RS274 Python/remap sources in src/emc/rs274ngc and from
# src/emc/pythonplugin/python_plugin.cc initialization/configuration paths,
# while compiling only the RS274 binding objects that need wasm-safe symbols.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
cxx=${CXX:-g++}
if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then
@@ -58,6 +58,10 @@ linuxcnc_source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-f
src/emc/rs274ngc/gcodemodule.cc)
mapfile -t linuxcnc_sources <<<"$linuxcnc_source_output"
python_binding_sources=("${linuxcnc_sources[@]:6}")
python_plugin_source_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \
src/emc/pythonplugin/python_plugin.cc)
mapfile -t python_plugin_sources <<<"$python_plugin_source_output"
python_token_sources=("${linuxcnc_sources[@]}" "${python_plugin_sources[@]}")
common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh)
mapfile -t common_flags <<<"$common_flag_output"
@@ -70,6 +74,12 @@ object_sources=(
"$python_c_api_shim"
"$runtime_shim"
)
probe_objects=(
"$build_dir/python_c_api_probe_main.o"
)
probe_sources=(
core/wasm_shims/python_c_api_probe_main.cc
)
for source in "${python_binding_sources[@]}"; do
object_name=$(basename "$source" .cc)
objects+=("$build_dir/$object_name.o")
@@ -88,9 +98,11 @@ next_signature="$build_dir/python-c-api.signature.next"
printf '\n'
printf 'SOURCES:\n'
printf '%s\n' "${object_sources[@]}"
printf 'PROBE_SOURCES:\n'
printf '%s\n' "${probe_sources[@]}"
} > "$next_signature"
if [[ ! -f "$signature" ]] || ! cmp -s "$signature" "$next_signature"; then
rm -f "$build_dir"/*.o "$build_dir"/*.d
rm -f "$build_dir"/*.o "$build_dir"/*.d "$build_dir/python_c_api_probe"
fi
mv "$next_signature" "$signature"
@@ -107,8 +119,11 @@ makefile="$build_dir/python-c-api.mk"
for object in "${objects[@]}"; do
printf 'DEP_FILES += %s.d\n' "$object"
done
for object in "${probe_objects[@]}"; do
printf 'DEP_FILES += %s.d\n' "$object"
done
printf '\n.PHONY: all\n'
printf 'all: $(OBJECTS)\n\n'
printf 'all: $(OBJECTS) %s\n\n' "$build_dir/python_c_api_probe"
object_index=0
for source in "${object_sources[@]}"; do
@@ -116,6 +131,14 @@ makefile="$build_dir/python-c-api.mk"
printf '\t@$(CXX) $(COMMON_FLAGS) -MMD -MP -MF %s.d -c %s -o %s\n\n' "${objects[$object_index]}" "$source" "${objects[$object_index]}"
object_index=$((object_index + 1))
done
probe_index=0
for source in "${probe_sources[@]}"; do
printf '%s: %s\n' "${probe_objects[$probe_index]}" "$source"
printf '\t@$(CXX) $(COMMON_FLAGS) -MMD -MP -MF %s.d -c %s -o %s\n\n' "${probe_objects[$probe_index]}" "$source" "${probe_objects[$probe_index]}"
probe_index=$((probe_index + 1))
done
printf '%s: %s %s\n' "$build_dir/python_c_api_probe" "$build_dir/python_c_api_shim.o" "${probe_objects[*]}"
printf '\t@$(CXX) %s %s -o %s\n\n' "$build_dir/python_c_api_shim.o" "${probe_objects[*]}" "$build_dir/python_c_api_probe"
printf '\n-include $(DEP_FILES)\n'
} > "$makefile"
make --output-sync=target -j"$build_jobs" -f "$makefile" >/dev/null
@@ -124,14 +147,14 @@ source_symbols_signature="$build_dir/python-c-api.source-symbols.signature"
source_symbols_next_signature="$build_dir/python-c-api.source-symbols.signature.next"
{
printf 'SOURCE_SYMBOL_RULE_VERSION=1\n'
printf 'LINUXCNC_SOURCES:\n'
printf '%s\n' "${linuxcnc_sources[@]}"
printf 'LINUXCNC_TOKEN_SOURCES:\n'
printf '%s\n' "${python_token_sources[@]}"
printf 'SOURCE_STATS:\n'
stat -c 'SOURCE=%n:%s:%y' "${linuxcnc_sources[@]}"
stat -c 'SOURCE=%n:%s:%y' "${python_token_sources[@]}"
} > "$source_symbols_next_signature"
if [[ ! -f "$source_symbols_signature" || ! -f "$build_dir/linuxcnc.py-tokens" ]] ||
! cmp -s "$source_symbols_signature" "$source_symbols_next_signature"; then
rg -o 'Py[A-Za-z0-9_]+' "${linuxcnc_sources[@]}" \
rg -o 'Py[A-Za-z0-9_]+' "${python_token_sources[@]}" \
| sed 's/^.*://' \
| LC_ALL=C sort -u \
> "$build_dir/linuxcnc.py-tokens"
@@ -185,12 +208,14 @@ awk '
$0 == "PyModuleDef_HEAD_INIT" { next }
$0 == "Py_TPFLAGS_DEFAULT" { next }
$0 == "PyCFunction" { next }
$0 == "PyConfig" { next }
$0 == "PyGetSetDef" { next }
$0 == "PyMemberDef" { next }
$0 == "PyMethodDef" { next }
$0 == "PyModuleDef" { next }
$0 == "PyMODINIT_FUNC" { next }
$0 == "PyObject_New" { next }
$0 == "PyStatus" { next }
$0 == "PyInit_emccanon" { next }
$0 == "PyInit_gcode" { next }
$0 == "PyInit_interpreter" { next }
@@ -229,4 +254,6 @@ if [[ -s "$build_dir/object.missing-python" ]]; then
exit 1
fi
"$build_dir/python_c_api_probe"
echo "linuxcnc wasm Python C API symbol probe passed"

View File

@@ -6,7 +6,9 @@ cd "$(dirname "$0")"
# LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and
# python_plugin.hh define the PythonPlugin API covered by this wrapper.
probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode python-plugin-link)
inittab_probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode python-plugin-inittab-link)
"$probe"
"$inittab_probe"
echo "linuxcnc wasm python_plugin link probe passed"

View File

@@ -11,6 +11,7 @@ probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode tooldata-link)
"$probe" 2>"$probe.err"
grep -F "tool_mmap_user(): tool mmap not available" "$probe.err" >/dev/null
grep -F "tooldata_put() no tool_mmap_base" "$probe.err" >/dev/null
grep -F "Error: tool_mmap_creator already called BYE" "$probe.err" >/dev/null
grep -F "!!!PROBLEM tool_nml_register()" "$probe.err" >/dev/null
grep -F "!!!!db_init: <" "$probe.err" >/dev/null
grep -F "not executable" "$probe.err" >/dev/null

View File

@@ -34,6 +34,19 @@ preflight_cache_dir="$build_dir/preflight-cache"
./check-linuxcnc-inputs-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-rs274-source-files.txt --require-rs274-complete
./check-linuxcnc-inputs-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-rs274-wasm-source-files.txt --require-rs274-complete
./check-linuxcnc-inputs-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-kinematics-source-files.txt --require-kinematics-complete
./check-linuxcnc-rs274-source-map.sh
./check-linuxcnc-kinematics-source-map.sh
./check-linuxcnc-opfs-source-map.sh
./check-linuxcnc-switchkins-remap-source-map.sh
./check-linuxcnc-wasm-smoke-source-map.sh
./check-linuxcnc-build-source-map.sh
./check-linuxcnc-browser-app-source-map.sh
./check-linuxcnc-workstreams-source-map.sh
./check-linuxcnc-tooldata-source-map.sh
./check-linuxcnc-wasm-shims-source-map.sh
./check-linuxcnc-wasm-blockers-source-map.sh
CNC_SIM_PREFLIGHT_CACHE_DIR="$preflight_cache_dir" CNC_SIM_WASM_BLOCKERS_SELF_CHECKS=1 \
./test-linuxcnc-wasm-blockers.sh linuxcnc-rs274-wasm-source-files.txt
./check-linuxcnc-switchkins-remap-table-cached.sh --cache-dir "$preflight_cache_dir" linuxcnc-kinematics-source-files.txt
grep -F 'tooldata:src/emc/tooldata/tooldata_nml.cc:NML tool table backend selected by LinuxCNC TOOL_NML_FLAG' linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'tooldata:src/emc/tooldata/tool_mmap_read.cc:LinuxCNC tooldata mmap reader utility source from tooldata Submakefile' linuxcnc-rs274-source-files.txt >/dev/null
@@ -67,6 +80,7 @@ grep -F 'LinuxCNC source basis: this probe derives required Python C API exports
grep -F 'LinuxCNC source basis: this probe compares wasm-safe tooldata runtime stubs' test-linuxcnc-wasm-tooldata-runtime-symbols.sh >/dev/null
grep -F 'LinuxCNC source basis: the standalone probe links src/emc/rs274ngc/interp_base.cc' test-linuxcnc-wasm-interp-base-link.sh >/dev/null
grep -F 'LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and' test-linuxcnc-wasm-python-plugin-link.sh >/dev/null
grep -F 'python-plugin-inittab-link' test-linuxcnc-wasm-python-plugin-link.sh >/dev/null
grep -F 'LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc is compiled directly' test-linuxcnc-wasm-rs274ngc-pre-object.sh >/dev/null
grep -F 'LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides the Interp' test-linuxcnc-wasm-rs274ngc-pre-link.sh >/dev/null
grep -F 'LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides read()' test-linuxcnc-wasm-runtime-link.sh >/dev/null

View File

@@ -160,6 +160,42 @@ for imported_module in (
if f'from "{imported_module}"' not in opfs_workspace:
print(f"browser smoke OPFS workspace module must import {imported_module}", file=sys.stderr)
sys.exit(1)
required_browser_sections = {
"web/test-browser-wasm-smoke-linuxcnc-sections.js": (
'runSection("linuxcnc browser basic parse"',
'runSection("linuxcnc generated switchkins cases load"',
'runSection("linuxcnc browser parse parameter scratch cleanup"',
),
"web/test-browser-wasm-smoke-opfs-basic-sections.js": (
'runSection("opfs program parse and restore"',
),
"web/test-browser-wasm-smoke-opfs-parameter-sections.js": (
'runSection("opfs parameter restore in new instance"',
'runSection("opfs default parameter backup"',
'runSection("opfs invalid parameter restore failures"',
'runSection("opfs stale parameter state cleanup"',
),
"web/test-browser-wasm-smoke-opfs-directory-sections.js": (
'runSection("opfs directory persist and load"',
'runSection("opfs metadata queries avoid wasm mirror pollution"',
),
"web/test-browser-wasm-smoke-opfs-mirror-sections.js": (
'runSection("opfs copy and move mirror files"',
),
"web/test-browser-wasm-smoke-opfs-policy-sections.js": (
'runSection("browser opfs policy"',
),
"web/test-browser-wasm-smoke-app-sections.js": (
'runSection("browser app opfs save"',
'runSection("browser app opfs restore"',
),
}
for section_module, required_sections in required_browser_sections.items():
section_text = Path(section_module).read_text(encoding="utf-8")
for required_section in required_sections:
if required_section not in section_text:
print(f"browser smoke module {section_module} missing required section {required_section}", file=sys.stderr)
sys.exit(1)
app_html = Path("web/index.html").read_text(encoding="utf-8")
if '<link rel="stylesheet" href="/styles.css" />' not in app_html:
print("browser app HTML must load /styles.css", file=sys.stderr)

View File

@@ -0,0 +1,6 @@
G21
F1
G0 X0 Y0 Z0
(Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc applies CENTER_ARC_RADIUS_TOLERANCE_MM from INI before interp_arc.cc checks IJK radius mismatch.)
G2 X2 Y0 I[1 - 0.0011]
M2

View File

@@ -0,0 +1,4 @@
G21 G90 G17
G0 X0 Y0
G93
G2 X1 Y0 R0.5

View File

@@ -0,0 +1,4 @@
G21 G90 G17
F100
G41.1 D1
G81 X1 Z-1 R1

View File

@@ -0,0 +1,4 @@
G21 G90 G17
F100
G93
G81 X1 Z-1 R1

View File

@@ -1,4 +1,7 @@
G21 G90 G17
(operator note)
G0 X0 Y0 Z1
G0 X1 (inline motion note)
G0 X2 ;semicolon motion note
;standalone semicolon note
M30

View File

@@ -0,0 +1,6 @@
G21 G90 G17
G64 P0.05 Q0.02
G64 P0.03
G61
G61.1
M30

View File

@@ -1,5 +1,6 @@
G21 G90 G17
G0 X5 Y6 Z7
G10 L20 P2 X1 Y2 Z3
(Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_setup computes G10 L20 linear offsets for X/Y/Z/U/V/W from the current position.)
G0 X5 Y6 Z7 U8 V9 W10
G10 L20 P2 X1 Y2 Z3 U4 V5 W6
G55
M30

View File

@@ -1,5 +1,5 @@
G21 G90 G17
G10 L2 P1 X12.5 Y-3 Z4 R30
G10 L2 P1 X12.5 Y-3 Z4 U7 V8 W9 R30
G92 X1 Y2 Z3
G92.1
M30

View File

@@ -1,4 +1,5 @@
G21 G90 G17
(Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_setup maps G10 P0 to the active coordinate system and stores U/V/W through PROGRAM_TO_USER_LEN.)
G55
G10 L2 P0 X7 Y8 Z9
G10 L2 P0 X7 Y8 Z9 U10 V11 W12
M30

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
G92 X1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
G64

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
G55

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
G53 G0 X0

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
G28

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M48

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M49

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M50 P0

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M50 P1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M51 P0

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M51 P1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M52 P0

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M52 P1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M53 P0

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M53 P1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M63 P1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M64 P1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M65 P1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M67 E0 Q1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
M68 E0 Q1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
G18

View File

@@ -0,0 +1,3 @@
G21 G90 G17
G41 D1
G43 H1

View File

@@ -0,0 +1,5 @@
G21 G90
(Source basis: LinuxCNC src/emc/rs274ngc/interp_read.cc rejects bare O-word definitions when DISABLE_FANUC_STYLE_SUB is enabled.)
O100
M99
M30

View File

@@ -0,0 +1,6 @@
G21 G90
(Source basis: LinuxCNC src/emc/rs274ngc/interp_read.cc rejects M98 when DISABLE_FANUC_STYLE_SUB is enabled.)
M98 P100
M30
O100
M99

View File

@@ -0,0 +1,7 @@
G21 G90
(Source basis: LinuxCNC src/emc/rs274ngc/interp_read.cc rejects Fanuc-style M99 when DISABLE_FANUC_STYLE_SUB is enabled.)
O100 sub
M99
O100 endsub
O100 call
M30

View File

@@ -0,0 +1,2 @@
[RS274NGC]
DISABLE_FANUC_STYLE_SUB = 1

View File

@@ -0,0 +1,2 @@
[RS274NGC]
DISABLE_G92_PERSISTENCE = 1

View File

@@ -0,0 +1,3 @@
G21 G90
G93
G1 X1

View File

@@ -0,0 +1,2 @@
G21 G90
G1 X1

View File

@@ -1,6 +1,7 @@
G21 G90 G17
G52 X1 Y2
G52 Y3
(Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_axis_offsets treats G52 offsets as sticky per-axis values, including U/V/W, and G92.2/G92.3 suspend and restore #5211-#5219.)
G52 X1 Y2 U4 V5
G52 Y3 V6
G92.2
G92.3
G92.1

View File

@@ -0,0 +1,3 @@
G21 G90 G17
F100
G5.3 G1 X1

View File

@@ -0,0 +1,3 @@
[RS274NGC]
G64_DEFAULT_TOLERANCE = 0.07
G64_DEFAULT_NAIVETOLERANCE = 0.04

View File

@@ -0,0 +1,4 @@
G21 G90 G17
(Source basis: LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc reads G64_DEFAULT_TOLERANCE and G64_DEFAULT_NAIVETOLERANCE for G64 without P/Q.)
G64
M30

View File

@@ -0,0 +1,7 @@
G21 G90 G17
F100
(Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_stop clears persisted G92/G52 parameters on program end.)
O10 if [[#5210 EQ 0] AND [#5211 EQ 0] AND [#5212 EQ 0] AND [#5213 EQ 0]]
G1 X1
O10 endif
M30

View File

@@ -0,0 +1,4 @@
G21 G90 G17
(Source basis: LinuxCNC src/emc/rs274ngc/interp_convert.cc convert_stop clears #5210-#5219 when DISABLE_G92_PERSISTENCE is enabled.)
G92 X4 Y5 Z6
M30

Some files were not shown because too many files have changed in this diff Show More