Compare commits

...

286 Commits

Author SHA1 Message Date
cnc
c81f9d0111 按源验证API百分号文件模式
源依据:LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc 的 Interp::open() 在首个非空行是 % 时启用 percent-file mode,src/emc/rs274ngc/interp_read.cc read_text() 在匹配 % 处结束读取并忽略后续文本。

结论:native API source-linked smoke 增加 CNC_SIM_RS274_FILE_MODE 下的百分号文件断言,确认 cnc_sim API 文件模式走 LinuxCNC open/read 语义而非逐行桥接或 smoke fallback。

检查:./test-linuxcnc-api-native.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 13:10:02 +08:00
cnc
c75eec1057 按源验证刀库DB参数拆分
源依据:LinuxCNC src/emc/tooldata/tooldata_db.cc 的 tooldata_db_init() 使用 strtok_r 原地拆分 progname_plus_args,再检查 argv[0] 是否可执行。

结论:wasm-safe tooldata runtime probe 增加多参数输入副作用断言,确认缺失 DB 程序返回失败前仍保留 LinuxCNC 的参数拆分边界,减少外部 tooldata DB 替换风险。

检查:./test-linuxcnc-wasm-tooldata-link.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 13:03:48 +08:00
cnc
23e67bb6cd 按源验证非随机刀表保存边界
源依据:LinuxCNC src/emc/tooldata/tooldata_common.cc 的 tooldata_save() 在 DB_NOTUSED 下按 is_random_toolchanger 选择起始 pocket;非随机换刀器从 pocket 1 开始写出,跳过 spindle pocket 0。

结论:wasm-safe tooldata_common standalone probe 增加非随机保存断言,确认 pocket 0 的 spindle-only 数据不会写入普通刀表,同时 pocket 1 的真实刀具仍会保存,减少 tooldata_common/mmap 后端替换风险。

检查:./test-linuxcnc-wasm-tooldata-common-link.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 12:58:57 +08:00
cnc
42e5b0784d 按源验证Python插件路径节边界
源依据:LinuxCNC src/emc/pythonplugin/python_plugin.cc 的 configure() 按调用方 section 查找 TOPLEVEL,但 PATH_APPEND 固定从 PYTHON section 逐项读取并在 tilde expansion 失败时返回 PLUGIN_EXCEPTION_DURING_PATH_APPEND。

结论:wasm-safe python_plugin standalone probe 增加 REMAP/PYTHON section 分离边界,确认禁用 Python 时仍保留 LinuxCNC 的 INI 查找顺序和失败状态,不引入浏览器端 Python 执行。

检查:./test-linuxcnc-wasm-python-plugin-link.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 12:53:40 +08:00
cnc
64f336d6f2 按源验证tooldata低索引写入
源依据:LinuxCNC src/emc/tooldata/tooldata_mmap.cc 的 tooldata_put() 仅在 idx > last_index 时返回 IDX_NEW 并更新 last_index,否则返回 IDX_OK。

结论:wasm-safe tooldata mmap probe 增加低于当前 last_index 的写入断言,确认返回 IDX_OK、last_index 不变且数据可读回,减少 tooldata_mmap 后端替换风险。

检查:./test-linuxcnc-wasm-tooldata-link.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 12:48:33 +08:00
cnc
fc56b11d8f 按源链接Python绑定探针
结论:依据 LinuxCNC canonmodule.cc、interpmodule.cc 与 py*.cc,新增 Python binding link probe,证明 7 个 non-gcodemodule blocked binding 对象可与 inert Python/runtime shims 链接;同时负向锁定 gcodemodule.cc 与浏览器 runtime canon bridge 的 Canon 回调重定义冲突,Python/remap 执行仍禁用,未扩展 smoke。

检查:./test-linuxcnc-wasm-python-binding-link.sh;./test-linuxcnc-wasm-python-c-api-symbols.sh;./test-linuxcnc-wasm-runtime-link.sh;git diff --check;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 12:40:49 +08:00
cnc
fb9561ccc7 按源标记Python绑定编译覆盖
结论:依据 LinuxCNC RS274 Python/Boost.Python 绑定源 canonmodule.cc、gcodemodule.cc、interpmodule.cc 与 py*.cc,blocker 分析新增 python-compile-covered 分组,锁定这些 blocker 已由 inert Python C API symbol probe 编译覆盖;Python/remap 执行仍保持禁用,未扩展 smoke。

检查:./test-linuxcnc-wasm-python-c-api-symbols.sh;./test-linuxcnc-wasm-blockers.sh linuxcnc-rs274-wasm-source-files.txt;CNC_SIM_WASM_BLOCKERS_SELF_CHECKS=1 ./test-linuxcnc-wasm-blockers.sh linuxcnc-rs274-wasm-source-files.txt;./check-linuxcnc-wasm-blockers-source-map.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 12:27:22 +08:00
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
cnc
f9ac38251b 参考所有分组,完成尽量多的内容
结论:source-map纳入WASM-safe core和blocked源清单,并由聚合守护比对linuxcnc-rs274-wasm-source-files.txt,防止WASM源码链接文档与manifest漂移。
2026-06-04 06:11:41 +08:00
cnc
002c8a2d53 参考所有分组,完成尽量多的内容
结论:OPFS负向守护补充showDirectoryPicker,禁止浏览器目录选择绕过OPFS持久化路径;未扩展smoke功能行为。
2026-06-04 05:59:49 +08:00
cnc
decc008609 参考所有分组,完成尽量多的内容
结论:聚合守护锁住LinuxCNC loadusr源依据,要求switchkins/remap生成器按do_loadusr_cmd getopt(+wWin:)和补全表跳过等待名后再记录实际程序。
2026-06-04 05:48:54 +08:00
cnc
3c2df90872 按提示词完善loadusr解析
结论:对齐LinuxCNC halcmd loadusr getopt(+wWin:)和补全表,生成器能正确跳过-Wn/-n等待名并提取实际用户态程序。
2026-06-04 05:37:51 +08:00
cnc
81c7b061a8 参考所有分组,完成尽量多的内容:结论为 native 守住 OPFS 选项不透传 2026-06-04 05:27:46 +08:00
cnc
2223e6d601 参考所有分组,完成尽量多的内容:结论为 manifest 校验覆盖 LinuxCNC loadusr 等待选项 2026-06-04 05:16:19 +08:00
cnc
708b2bee17 参考所有分组,完成尽量多的内容:结论为 binding manifest 纳入 source-map 守护 2026-06-03 23:39:09 +08:00
cnc
0a270c73a0 参考所有分组,完成尽量多的内容:结论为 source-map 守住 RS274 core manifest 2026-06-03 23:20:46 +08:00
cnc
52d7a49861 参考所有分组,完成尽量多的内容:结论为 porting 文档守住 LinuxCNC fixture 覆盖 2026-06-03 23:07:37 +08:00
cnc
802e85cd63 参考所有分组,完成尽量多的内容:结论为动态守住 LinuxCNC fixture 双路径覆盖 2026-06-03 22:55:55 +08:00
cnc
0de612651e 参考所有分组,完成尽量多的内容:结论为 guardrail 锁住 native 注释百分号覆盖 2026-06-03 22:45:45 +08:00
cnc
6f21d22912 参考所有分组,完成尽量多的内容:结论为 native RS274 补齐注释百分号错误覆盖 2026-06-03 22:37:13 +08:00
cnc
f8902b3f8d 参考所有分组,完成尽量多的内容:结论为 build-wasm 显式关闭 smoke 后端 2026-06-03 22:24:57 +08:00
cnc
3d368b2564 参考所有分组,完成尽量多的内容:结论为 WASM source probe 禁止夹带 smoke 后端 2026-06-03 22:07:57 +08:00
cnc
8b89b5b4c6 参考所有分组,完成尽量多的内容:结论为 switchkins 表检查同步守住 kinematics manifest 完整性 2026-06-03 21:44:41 +08:00
cnc
21c242c9a4 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:按 LinuxCNC rs274ngc_pre.cc save_parameters()/restore_parameters() 的参数文件流程,收紧 OPFS 参数持久化后 WASM 根目录 scratch 文件清理;未扩展临时 smoke 行为。
2026-06-03 21:32:27 +08:00
cnc
945e839e9d 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:对齐 LinuxCNC tooldata_nml.cc 与 tooldata_db.cc 的失败诊断输出,收紧 WASM tooldata link probe;未扩展临时 smoke 行为。
2026-06-03 21:19:24 +08:00
cnc
feb04102e7 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 cached LinuxCNC input 与 switchkins/remap 表检查器的 filesystem-root cache-dir 拒绝守卫,不新增 CNC 行为或 smoke 功能。
2026-06-03 21:08:35 +08:00
cnc
aaa2573bba 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 check-linuxcnc-inputs-cached.sh 的 cache-dir 入口拒绝守卫,锁住缺失 cache-dir 值和空白预检缓存目录,不新增 CNC 行为或 smoke 功能。
2026-06-03 20:57:15 +08:00
cnc
d7b9006ba5 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 LinuxCNC source file/CXX flag helper 的入口拒绝守卫,锁住缺失参数、未知选项、逃逸 source path 和未知 WASM profile,不新增 CNC 行为或 smoke 功能。
2026-06-03 20:47:14 +08:00
cnc
51b415a701 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 Puma560 DH 参数生成器的可执行入口和 LinuxCNC-root 相对 HAL 源约束,锁住 puma560_dh.hal 中 genserkins DH 值到生成输出的映射,不新增 CNC 行为或 smoke 功能。
2026-06-03 20:36:09 +08:00
cnc
856b526acb 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 switchkins/remap 表 generator 的 CLI 拒绝守卫,锁住未知选项、缺失 all-output-dir、过多参数和空白输出目录,不新增 CNC 行为或 smoke 功能。
2026-06-03 20:03:43 +08:00
cnc
bc54b5f8f8 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 switchkins/remap 表 checker 与 cached checker 的 CLI 拒绝守卫,锁住未知选项、多余参数、缺失 cache-dir 和空白缓存目录,不新增 CNC 行为或 smoke 功能。
2026-06-03 19:55:53 +08:00
cnc
8151829643 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补齐 kinematics manifest lister 对复合过滤器 core,header 的拒绝守卫,保持与通用 LinuxCNC manifest 组名校验一致。
2026-06-03 19:46:40 +08:00
cnc
5fb041f1a5 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:收敛第 5/6 组 Node/browser smoke wrapper 约束,all-native guardrail 验证 WASM_NODE_SMOKE_MIN_STEPS 和 BROWSER_SMOKE_MIN_SECTIONS 非法阈值会被 wrapper 直接拒绝,避免 smoke 覆盖门槛被误配置;未扩展 smoke 功能。验证:CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-03 19:37:23 +08:00
cnc
bba306ccfe 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:收敛第 6 组 WASM manifest/source-link 约束,WASM manifest source lister 明确拒绝 core,header 这类复合 filter,避免 source coverage 查询静默误用;未扩展 smoke 功能。验证:./test-linuxcnc-wasm-cmake-safe-probe.sh linuxcnc-rs274-wasm-source-files.txt、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-03 19:28:43 +08:00
cnc
4a3d87045e 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:收敛第 3/5/6 组 OPFS/browser 约束,test-native guardrail 明确要求 wasm-core OPFS 路径拒绝反斜杠和 NUL 字符,防止 browser WASM filesystem 路径逃逸;未扩展 smoke 功能。验证:./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-03 19:24:13 +08:00
cnc
951dcc7980 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:收敛第 1 组 switchkins/remap 生成器约束,--all-output-dir 拒绝含空白路径和文件系统根目录,避免生成表/配置 case 误写到不受控位置;未扩展 smoke 功能。验证:./check-linuxcnc-switchkins-remap-table.sh linuxcnc-kinematics-source-files.txt、CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-03 19:17:18 +08:00
cnc
aa6e79ac64 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:收敛第 2/6 组 manifest/source-link 约束,公共 manifest source lister 只接受单个 manifest group 名称或 all,拒绝 core,header 这类复合 filter,避免 source coverage 查询静默误用;未扩展 smoke 功能。验证:CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh、./test-native.sh、./test-linuxcnc-source-link.sh 均通过。
2026-06-03 19:11:22 +08:00
cnc
39f6f9133c 参考所有分组,完成尽量多的内容:收紧源列表过滤器 basename 守卫,结论测试通过 2026-06-03 18:54:36 +08:00
cnc
bc7b947d14 参考所有分组,完成尽量多的内容:收紧 CMake 源列表参数守卫,结论测试通过 2026-06-03 18:46:10 +08:00
cnc
1ce460d1d5 参考所有分组,完成尽量多的内容:收紧 CMake 源列表缺失守卫,结论测试通过 2026-06-03 18:36:59 +08:00
cnc
96b5973e1b 参考所有分组,完成尽量多的内容:收紧项目源列表路径守卫,结论测试通过 2026-06-03 18:27:29 +08:00
cnc
c8b834e36b 参考所有分组,完成尽量多的内容:补齐 switchkins 缓存签名输入
结论:switchkins/remap 表缓存签名现在包含 manifest 输入校验脚本,输入校验规则变化会使缓存失效;未扩展 smoke 功能。
2026-06-03 18:13:42 +08:00
cnc
32b4a96a9f 参考所有分组,完成尽量多的内容:收紧 switchkins 生成器 manifest 入口校验
结论:switchkins/remap 生成器现在先复用 LinuxCNC manifest 输入校验,坏行、未知组、重复源等会在生成 table/config/json 前被拦截;未扩展 smoke 功能。
2026-06-03 18:06:21 +08:00
cnc
ee0a3130bf 参考所有分组,完成尽量多的内容:收紧 switchkins 表 manifest 入口校验
结论:switchkins/remap 表 checker 现在先复用 LinuxCNC manifest 输入校验,坏行、未知组、重复源等会在表扫描前被拦截;未扩展 smoke 功能。
2026-06-03 17:49:42 +08:00
cnc
c031bc0a2c 参考所有分组,完成尽量多的内容:收紧 wasm blocker manifest 校验
结论:wasm blocker 分析器现在与 manifest 输入校验一致拒绝缺少 note 或字段数量异常的坏行,并在 blocker self-check 中覆盖该负向路径;未扩展 smoke 功能。
2026-06-03 17:43:51 +08:00
cnc
dcf246c0a0 参考所有分组,完成尽量多的内容:收紧 manifest 坏行校验
结论:通用 LinuxCNC manifest lister 现在与输入校验一致拒绝缺少 note 或字段数量异常的坏行,并在 all-native guardrail 中覆盖通用 lister 与 native/source-link 调用链;未扩展 smoke 功能。
2026-06-03 17:37:58 +08:00
cnc
1f4044efe2 参考所有分组:锁定 OPFS parseFileWithParameterFile 路径守卫,测试通过 2026-06-03 17:08:42 +08:00
cnc
3cc7590121 参考所有分组:锁定loadusr资产清单校验,测试通过 2026-06-03 17:00:15 +08:00
cnc
180c0f6dbe 收紧TRSRN资产清单校验 2026-06-03 16:44:33 +08:00
cnc
702f94cc52 参考 OPFS 参数桥接:锁定 LinuxCNC 临时参数目录守卫,测试通过 2026-06-03 16:42:05 +08:00
cnc
b366d220f5 参考所有分组:锁定浏览器入口和应用恢复守卫,测试通过 2026-06-03 16:28:11 +08:00
cnc
3090b9a79a 参考所有分组:锁定浏览器 switchkins 和应用 OPFS 守卫,测试通过 2026-06-03 16:19:04 +08:00
cnc
c851476007 参考所有分组:锁定 OPFS 参数失败清理守卫,测试通过 2026-06-03 16:09:22 +08:00
cnc
ddc20eeb98 参考所有分组:锁定 OPFS 目录和参数浏览器守卫,测试通过 2026-06-03 16:03:04 +08:00
cnc
1ef3f2c7df 参考所有分组:锁定 OPFS basic 浏览器守卫,测试通过 2026-06-03 15:56:39 +08:00
cnc
eef30e1831 参考所有分组:锁定 OPFS policy 负向守卫,测试通过 2026-06-03 15:49:36 +08:00
cnc
1433435121 参考所有分组:锁定 OPFS 和烟测覆盖守卫,测试通过 2026-06-03 15:43:23 +08:00
cnc
46807f05a5 继续按 align-linuxcnc 约束:补 OPFS 镜像跨实例浏览器验证,测试通过 2026-06-03 15:31:05 +08:00
cnc
77f9754833 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:补强 tracked switchkins/remap 输出的 TOOL_TABLE source-only 守护,防止仓库内已生成表绕过 LinuxCNC source policy;表校验、native、source-link 均通过。
2026-06-03 15:20:57 +08:00
cnc
457557dee8 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:按 LinuxCNC source policy 将 TOOL_TABLE 恢复为 source coverage only,撤回生成 API/browser switchkins cases,并增加 checker/native 守护;表校验、native、source-link 均通过。
2026-06-03 15:13:01 +08:00
cnc
aff6f85d8a 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:按 LinuxCNC INI TOOL_TABLE 来源扩展 switchkins/remap 配置别名和生成案例,未增加 smoke 功能行为;表校验、native、source-link 均通过。
2026-06-03 14:53:37 +08:00
cnc
4086445900 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:按 align-linuxcnc 约束补齐 switchkins TOOL_TABLE/tooldata 与 kinematics manifest lister 的源码政策说明,并用 all-native guardrail 锁定文档,避免规则和脚本脱节;不扩展 smoke 行为。

源依据:LinuxCNC M428/M429/M430 INI 的 [EMCIO] TOOL_TABLE 条目、linuxcnc-kinematics-source-files.txt 的 asset/tooldata/generated 分组,以及 check-linuxcnc-switchkins-remap-table-cached.sh 对引用源的签名约束。

验证:CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh 通过;./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-03 13:47:51 +08:00
cnc
18f14ca111 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:按 align-linuxcnc 约束增强 kinematics manifest lister 的 all-native 守卫,覆盖 asset/tooldata/generated 分组、full 输出以及未知 filter/output 负例;只收紧源清单约束,不扩展 smoke 行为。

源依据:linuxcnc-kinematics-source-files.txt 中由 ../linuxcnc/configs/sim、src/hal/user_comps、src/objects/hal/components 等 LinuxCNC 源/生成源组成的 kinematics、switchkins、TOOL_TABLE 覆盖。

验证:CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 ./test-all-native.sh 通过;./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-03 13:42:26 +08:00
cnc
7d28d0a025 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:按 align-linuxcnc 约束让 switchkins/remap 生成器读取 LinuxCNC INI [EMCIO] TOOL_TABLE,仅用于校验 linuxcnc-kinematics-source-files.txt 的 tooldata 源覆盖;不把 TOOL_TABLE 输出为新 API case,不扩展 smoke 行为。

源依据:../linuxcnc/configs/sim 下 M428/M429/M430 switchkins INI 的 [EMCIO] TOOL_TABLE 条目,以及现有 kinematics manifest 的 tooldata 条目。

验证:./check-linuxcnc-switchkins-remap-table.sh linuxcnc-kinematics-source-files.txt 通过;./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-03 13:36:25 +08:00
cnc
0d4e248811 参考所有分组,完成尽量多的内容。禁止顺手扩功能 smoke
结论:按 align-linuxcnc 约束收窄 OPFS removeFile 和 LinuxCNC 参数 .new/.bak 清理为文件专用语义,避免把同名目录递归删除。依据 ../linuxcnc/src/emc/rs274ngc/rs274ngc_pre.cc save_parameters() 的 fopen(filename.new)、unlink(filename.bak)、link、rename 行为。

验证:./test-native.sh 通过;./test-linuxcnc-source-link.sh 通过。
2026-06-03 13:28:15 +08:00
cnc
5481ab3bff 参考所有分组:锁定kinematics源约束守护
结论:按 align-linuxcnc 约束把 generated component manpage metadata 和已知不可解析 TOOL_TABLE 基线纳入 native/all-native guardrail,防止 kinematics manifest/source 校验被削弱。

验证:check-linuxcnc-inputs --require-kinematics-complete、check-linuxcnc-switchkins-remap-table、CNC_SIM_ALL_NATIVE_GUARDRAILS_ONLY=1 test-all-native、test-native、test-linuxcnc-source-link 均通过。
2026-06-03 11:36:09 +08:00
cnc
8ff22065dd 参考所有分组:补生成组件manpage源约束
结论:按 align-linuxcnc 约束补齐 kinematics manifest 的 halcompile generated manpage metadata,并让 M428/M429/M430 INI TOOL_TABLE 的不可解析 LinuxCNC 源现状成为显式基线。

验证:check-linuxcnc-inputs --require-kinematics-complete、check-linuxcnc-switchkins-remap-table、test-native、test-linuxcnc-source-link 均通过。
2026-06-03 11:29:47 +08:00
cnc
5c2d5cfa05 参考所有分组:保持OPFS备份失败非致命
结论:按 LinuxCNC rs274ngc_pre.cc save_parameters() 中 unlink/link 失败不阻止 rename 主参数文件的语义,OPFS 参数 .bak 写入失败时不让解析保存失败,并避免把目录当作可 unlink 的备份文件删除;未扩展 smoke 解析行为。

验证:./test-native.sh;./test-linuxcnc-source-link.sh;./test-web-wasm-browser-smoke.sh。
2026-06-03 11:22:08 +08:00
cnc
f98a715ed7 参考所有分组:替换陈旧OPFS参数备份
结论:按 LinuxCNC rs274ngc_pre.cc save_parameters() 先 unlink 旧 .bak 再 link 当前参数文件的语义,浏览器 OPFS 成功保存时总是用解析前主参数文件刷新 .bak;未扩展 smoke 解析行为。

验证:./test-native.sh;./test-linuxcnc-source-link.sh;./test-web-wasm-browser-smoke.sh。
2026-06-03 11:11:40 +08:00
cnc
1928ff3b6a 参考所有分组:补充OPFS失败解析备份断言
结论:按 LinuxCNC rs274ngc_pre.cc 仅成功解释后执行 save_parameters() 的语义,验证浏览器 OPFS 参数 .bak 不会在失败解析后被替换;未扩展 smoke 解析行为。

验证:./test-native.sh;./test-linuxcnc-source-link.sh;./test-web-wasm-browser-smoke.sh。
2026-06-03 11:04:35 +08:00
cnc
6cf7c73392 参考所有分组:完善OPFS参数备份桥接
结论:按 LinuxCNC rs274ngc_pre.cc save_parameters()/restore_parameters() 语义,补齐浏览器 OPFS 参数文件 .bak 持久化;未扩展 smoke 解析行为。

验证:./test-native.sh;./test-linuxcnc-source-link.sh;./test-web-wasm-node-smoke.sh;./test-web-wasm-browser-smoke.sh。
2026-06-03 10:58:05 +08:00
cnc
028825cd9c 参考所有分组:补 source 覆盖文档登记,测试通过 2026-06-03 10:21:39 +08:00
cnc
e778a37fdb 参考所有分组:补 native numeric O-word source 覆盖,测试通过 2026-06-03 10:17:10 +08:00
cnc
6cc07c2950 参考所有分组:补 native read path source 覆盖,测试通过 2026-06-03 10:10:45 +08:00
cnc
c5a1d655b0 参考所有分组:补 program end source 覆盖,测试通过 2026-06-03 10:04:46 +08:00
cnc
0895c04ab1 参考所有分组:补 G4 dwell source 边界覆盖,测试通过 2026-06-03 09:58:44 +08:00
cnc
c932e10801 参考所有分组:补 modal group source 覆盖,测试通过 2026-06-03 09:41:26 +08:00
cnc
d1e0152eb8 参考所有分组:补 G10 tool source 覆盖,测试通过 2026-06-03 09:36:47 +08:00
cnc
a134dcef7d 参考所有分组:补 G10 source 覆盖,测试通过 2026-06-03 09:32:16 +08:00
cnc
bbd5739e7a 参考所有分组:补 polar 和 NURBS source 覆盖,测试通过 2026-06-03 09:27:04 +08:00
cnc
611e6417b4 参考所有分组:补 NURBS source 覆盖,测试通过 2026-06-03 09:22:26 +08:00
cnc
fa4296d730 参考所有分组:补 percent 错误 source 覆盖,测试通过 2026-06-03 09:12:17 +08:00
cnc
2dd9847a7b 参考所有分组:补 comment 错误 source 覆盖,测试通过 2026-06-03 09:04:26 +08:00
cnc
9bf9206869 参考所有分组:补 percent 文件 source 覆盖,测试通过 2026-06-03 08:56:30 +08:00
cnc
090a5fb89c 参考所有分组:补 close_and_downcase word source 覆盖,测试通过 2026-06-03 08:49:10 +08:00
cnc
2f7a46758b 参考所有分组:补 extended N 行号 source 覆盖,测试通过 2026-06-03 08:41:44 +08:00
cnc
1b16d3e0b5 参考所有分组:补 fractional N 行号 source 覆盖,测试通过 2026-06-03 08:33:33 +08:00
cnc
d0a7a9fcec 参考所有分组:补 G73 G85 G86 G89 source 覆盖,测试通过 2026-06-03 06:30:24 +08:00
cnc
dbf27a4e6b 参考所有分组:补 native 参数持久化和 threading 覆盖,测试通过 2026-06-03 06:21:35 +08:00
cnc
e71b9146e1 参考所有分组:补 G76 和 NURBS native 覆盖,测试通过 2026-06-03 06:09:20 +08:00
cnc
59287b9920 参考所有分组:补只读命名参数 native 覆盖,测试通过 2026-06-03 06:00:29 +08:00
cnc
33e1ee5a06 参考所有分组:补执行错误 native 覆盖,测试通过 2026-06-03 00:55:59 +08:00
cnc
b26620dc32 参考所有分组:补 read_text native 覆盖,测试通过 2026-06-03 00:48:18 +08:00
cnc
74cb5f826c 参考所有分组:补读取和圆弧 native 覆盖,测试通过 2026-06-03 00:35:07 +08:00
cnc
2ba8cd7fb8 参考所有分组:补编号参数 native 覆盖,测试通过 2026-06-03 00:26:07 +08:00
cnc
e3d47e8267 参考所有分组:补 O-word 状态参数 native 覆盖,测试通过 2026-06-03 00:19:16 +08:00
cnc
3df61e8da5 参考所有分组:补齐 O-word native 覆盖,测试通过 2026-06-03 00:12:28 +08:00
cnc
28aa1956b7 继续按 align-linuxcnc 约束:补块读取 native 覆盖,测试通过
结论:补齐 LinuxCNC interp_read.cc read_items()/read_one_item() 坏字符路径,以及 interp_check.cc check_m_codes() M 码数量错误的 native 校验;./test-native.sh 与 ./test-linuxcnc-source-link.sh 均通过,未扩展 smoke。
2026-06-03 00:01:25 +08:00
cnc
4e3df07b8a 继续按 align-linuxcnc 约束:补参数表达式 native 覆盖,测试通过
结论:补齐 LinuxCNC interp_read.cc 参数读写、ATAN/unary/binary 表达式错误路径,以及 interp_execute.cc sqrt/divide/power 执行错误的 native 校验;./test-native.sh 与 ./test-linuxcnc-source-link.sh 均通过,未扩展 smoke。
2026-06-02 23:56:34 +08:00
cnc
b7610d1b96 继续按 align-linuxcnc 约束:补极坐标 native 覆盖,测试通过
结论:补齐 LinuxCNC interp_read.cc、interp_internal.cc、interp_find.cc 极坐标路径,以及 interp_convert.cc 扩展平面 arc/NURBS 错误 fixture 的 native 校验;./test-native.sh 与 ./test-linuxcnc-source-link.sh 均通过,未扩展 smoke。
2026-06-02 23:51:22 +08:00
cnc
55c75ffe80 继续按 align-linuxcnc 约束:补固定循环 native 覆盖,测试通过
结论:补齐 LinuxCNC interp_cycles.cc 固定循环错误 fixture 的 native 校验,覆盖重复次数、平面轴/R、Q/P、主轴状态和 G87 必需字;./test-native.sh 与 ./test-linuxcnc-source-link.sh 均通过,未扩展 smoke。
2026-06-02 23:47:02 +08:00
cnc
ad3720713e 继续按 align-linuxcnc 约束:补 M 码和刀具 native 覆盖,测试通过
结论:补齐 LinuxCNC interp_convert.cc convert_m()、convert_tool_select()、convert_tool_length_offset() 以及 interp_find.cc find_tool_index() 相关错误 fixture 的 native 校验;./test-native.sh 与 ./test-linuxcnc-source-link.sh 均通过,未扩展 smoke。
2026-06-02 23:42:05 +08:00
cnc
ba526a6042 继续按 align-linuxcnc 约束:补轴运动 native 覆盖,测试通过
结论:补齐 LinuxCNC interp_internal.cc enhance_block() 轴/运动错误 fixture 的 native 校验,并同步 check_other_codes() native 覆盖文档;./test-native.sh 与 ./test-linuxcnc-source-link.sh 均通过。
2026-06-02 23:34:56 +08:00
cnc
93f2a903b5 继续按 align-linuxcnc 约束:补未使用词 native 覆盖,测试通过 2026-06-02 23:25:47 +08:00
cnc
c52ecc09eb 继续按 align-linuxcnc 约束:补词读取 native 覆盖,测试通过 2026-06-02 23:20:55 +08:00
cnc
cc05532528 继续按 align-linuxcnc 约束:补 GM 读码 native 覆盖,测试通过 2026-06-02 23:16:15 +08:00
cnc
ecec27aa64 继续按 align-linuxcnc 约束:补 G53 校验 native 覆盖,测试通过 2026-06-02 23:11:47 +08:00
cnc
c25b0eff22 继续按 align-linuxcnc 约束:补 M98 与增量圆弧源码覆盖,测试通过 2026-06-02 23:07:27 +08:00
cnc
777d29651c 继续按 align-linuxcnc 约束:补 G 代码校验错误源码覆盖,测试通过 2026-06-02 22:34:52 +08:00
cnc
138fe862ae 继续按 align-linuxcnc 约束:补工具号查找错误源码覆盖,测试通过 2026-06-02 22:29:47 +08:00
cnc
d985b13a8e 继续按 align-linuxcnc 约束:补工具长度错误源码覆盖,测试通过 2026-06-02 22:25:05 +08:00
cnc
030074b8c5 继续按 align-linuxcnc 约束:补极坐标变体源码覆盖,测试通过 2026-06-02 22:20:03 +08:00
cnc
84fdc8a1c5 继续按 align-linuxcnc 约束:补增强块轴值错误源码覆盖,测试通过 2026-06-02 22:15:29 +08:00
cnc
9c03f91fa8 继续按 align-linuxcnc 约束:补极坐标源码覆盖,测试通过 2026-06-02 22:08:26 +08:00
cnc
868f20382a 继续按 align-linuxcnc 约束:补螺纹转换错误源码覆盖,测试通过 2026-06-02 20:15:04 +08:00
cnc
cde0bdaba6 继续按 align-linuxcnc 约束:补螺纹和恒线速校验源码覆盖,测试通过 2026-06-02 20:08:39 +08:00
cnc
3a60624108 继续按 align-linuxcnc 约束:补未使用字地址源码覆盖,测试通过 2026-06-02 19:53:28 +08:00
cnc
4d0cf788b1 继续按 align-linuxcnc 约束:补主轴选择和 M19 校验源码覆盖,测试通过 2026-06-02 19:47:52 +08:00
cnc
e875496fe0 继续按 align-linuxcnc 约束:补 convert_m 参数错误源码覆盖,测试通过 2026-06-02 19:36:22 +08:00
cnc
7e15a99d28 继续按 align-linuxcnc 约束:补控制字位置错误源码覆盖,测试通过 2026-06-02 18:49:53 +08:00
cnc
3435c3556a 继续按 align-linuxcnc 约束:补 reader 裸项错误源码覆盖,测试通过 2026-06-02 18:43:54 +08:00
cnc
318220b766 继续按 align-linuxcnc 约束:补 O-word 调用错误源码覆盖,测试通过 2026-06-02 18:33:15 +08:00
cnc
43a3e66106 继续按 align-linuxcnc 约束:补注释前缀控制字源码覆盖,测试通过 2026-06-02 18:27:50 +08:00
cnc
4f1042c548 继续按 align-linuxcnc 约束:补 O-word 匹配错误源码覆盖
结论:新增 undefined/no-matching O-word label 与子程序闭合方式错误的 source-link 断言;依据 LinuxCNC interp_o_word.cc,未扩展 smoke。
2026-06-02 18:01:07 +08:00
cnc
cc708b1ccc 继续按 align-linuxcnc 约束:补 O-word 执行错误源码覆盖
结论:新增重复/未匹配 O-word label 和 out-of-context return/endsub 的 source-link 断言;依据 LinuxCNC interp_o_word.cc,未扩展 smoke。
2026-06-02 17:55:14 +08:00
cnc
25519b5bc4 继续按 align-linuxcnc 约束:补文件读取错误源码覆盖
结论:新增 read_text 和 read_n_number 的 EOF、百分号文件、过长行和 N 行号错误 source-link 断言;依据 LinuxCNC interp_read.cc,未扩展 smoke。
2026-06-02 17:47:24 +08:00
cnc
a44199eb48 继续按 align-linuxcnc 约束:补 O-word 读取错误源码覆盖
结论:新增 malformed O-word、缺少控制表达式括号、O-call 参数过多和 M98 缺 P 的 source-link 断言;依据 LinuxCNC interp_read.cc,未扩展 smoke。
2026-06-02 17:35:18 +08:00
cnc
9a9b931982 继续按 align-linuxcnc 约束:补参数读取错误源码覆盖
结论:新增参数索引、命名参数终止和实数读取错误 fixture,全部绑定 LinuxCNC interp_read.cc 的 source-link 断言;未扩展 smoke。
2026-06-02 17:20:42 +08:00
cnc
7bd1dc640f 继续按 align-linuxcnc 约束:补 block reader 源码错误覆盖
结论:覆盖 LinuxCNC interp_read.cc/read_dollar()/read_one_item() 的重复 $、非法起始字符和中途 N word 错误,以及 interp_check.cc/check_m_codes() 的 M 码数量错误;未扩展 smoke。

验证:./test-linuxcnc-source-link.sh;./test-native.sh。
2026-06-02 17:00:55 +08:00
cnc
b83bc47c45 继续按 align-linuxcnc 约束:补 G/M reader 源码错误覆盖
结论:覆盖 LinuxCNC interp_read.cc/read_g()/read_m() 的范围、未知代码和同 modal group 冲突错误,代码表依据 interp_array.cc;未扩展 smoke。

验证:./test-linuxcnc-source-link.sh;./test-native.sh。
2026-06-02 16:35:58 +08:00
cnc
6ffcada028 继续按 align-linuxcnc 约束:补齐重复 word reader 源码覆盖
结论:覆盖 LinuxCNC interp_read.cc 中剩余重复 A/B/C/D/E/I/J/K/P/Q/R/U/V/W word reader 错误;未扩展 smoke。

验证:./test-linuxcnc-source-link.sh;./test-native.sh。
2026-06-02 16:30:39 +08:00
cnc
b2945cf57f 继续按 align-linuxcnc 约束:补 unary 和参数读取源码错误覆盖
结论:覆盖 LinuxCNC interp_read.cc 中 read_operation_unary() 未知词分支、read_parameter_setting() 缺少等号和 read_real_value() 空值错误;未扩展 smoke。

验证:./test-linuxcnc-source-link.sh;./test-native.sh。
2026-06-02 16:25:57 +08:00
cnc
05ece027c4 继续按 align-linuxcnc 约束:补 word reader 源码错误覆盖
结论:覆盖 LinuxCNC interp_read.cc 中负数 F/G/H/L/M/S/T 和重复 F/H/L/S/T/X/Y/Z word reader 错误;未扩展 smoke。

验证:./test-linuxcnc-source-link.sh;./test-native.sh。
2026-06-02 16:17:19 +08:00
cnc
b4d0bfae46 继续按 align-linuxcnc 约束:补齐 read_operation 源码错误覆盖
结论:覆盖 LinuxCNC interp_read.cc read_operation() 的未知二元操作名前缀、未知操作字符和未闭合表达式错误;未扩展 smoke。

验证:./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-02 16:10:03 +08:00
cnc
3f5c3faf25 继续按 align-linuxcnc 约束:补 unary 错误源码回归 2026-06-02 16:01:55 +08:00
cnc
dd4c62fce6 继续按 align-linuxcnc 约束:补 ATAN 错误源码回归 2026-06-02 15:58:01 +08:00
cnc
4861287138 继续按 align-linuxcnc 约束:补表达式错误源码回归 2026-06-02 15:54:17 +08:00
cnc
7319e18810 继续按 align-linuxcnc 约束:补表达式运算源码回归 2026-06-02 15:50:37 +08:00
cnc
df5c459b1d 继续按 align-linuxcnc 约束:补只读命名参数源回归
结论:新增 LinuxCNC-backed readonly named parameter fixture,依据 interp_namedparams.cc::lookup_named_param 与 init_readonly_param,在 native librs274 与 source-link runner 中验证 #<_metric>/#<_imperial>/#<_absolute>/#<_incremental>/#<_feed>/#<_rpm>/#<_spindle_on>/#<_spindle_cw>;未扩展 smoke,test-native 与 test-linuxcnc-source-link 均通过。
2026-06-02 15:40:23 +08:00
cnc
23284e528f 继续按 align-linuxcnc 约束:补 G7G76 源链接回归
结论:新增 LinuxCNC-backed G7 G76 fixture,依据 interp_convert.cc::convert_lathe_diameter_mode 和 convert_threading_cycle 验证直径模式下 G76 I/J/K 几何缩放、最终 threading pass 与 taper sync pitch;未扩展 smoke,test-native 与 test-linuxcnc-source-link 均通过。
2026-06-02 15:33:17 +08:00
cnc
952d3ecfd2 继续按 align-linuxcnc 约束:同步只读参数覆盖文档
结论:文档矩阵已同步 G7/G8 提交结果,#<_lathe_diameter_mode> 与 #<_lathe_radius_mode> 标记为 LinuxCNC native/source-backed 覆盖;仅文档改动,未运行测试。
2026-06-02 15:28:42 +08:00
cnc
6e0a5627f0 继续按 align-linuxcnc 约束:补 G7G8 源链接回归
结论:新增 LinuxCNC-backed lathe diameter/radius fixture,依据 interp_convert.cc::convert_lathe_diameter_mode 与 interp_namedparams.cc readonly 参数,在 native librs274 与 source-link runner 中验证 G7/G8 同块 X 缩放和 #<_lathe_diameter_mode>/#<_lathe_radius_mode> 分支;未扩展 smoke,test-native 与 test-linuxcnc-source-link 均通过。
2026-06-02 15:27:31 +08:00
cnc
6266887fec 继续按 align-linuxcnc 约束:锁定 INI remap smoke 过滤依据
结论:Node/browser generated switchkins smoke 的 INI_FILE_NAME 过滤已用 LinuxCNC rs274ngc_pre.cc ini_load 来源注释标明,并由 native/all-native guardrail 锁定;native/source-link/表检查均通过。
2026-06-02 15:17:28 +08:00
cnc
b8ad3a0346 继续按 align-linuxcnc 约束:闭合 switchkins 构建预检
结论:build-wasm 入口纳入 cached switchkins/remap 表校验,LinuxCNC RS274 INI 加载按 rs274ngc_pre.cc 保留真实 ini_load 路径,Node/browser generated switchkins smoke 跳过会触发 REMAP 依赖加载的 INI_FILE_NAME 别名;native/source-link/表检查均通过。
2026-06-02 15:13:37 +08:00
cnc
0abf801856 按提示词结论补 switchkins 完整覆盖锁 2026-06-02 14:40:52 +08:00
cnc
31e1e4499e 按提示词结论补构建校验依据锁 2026-06-02 14:26:40 +08:00
cnc
4780dd8621 按提示词结论补源链接脚本依据锁 2026-06-02 14:19:08 +08:00
cnc
303b6bfa36 按提示词结论补项目源集依据锁 2026-06-02 14:11:22 +08:00
cnc
854cc682b3 按提示词结论补源路径依据锁 2026-06-02 14:06:59 +08:00
cnc
dda5b4973e 按提示词结论补 flags helper 源依据锁 2026-06-02 14:02:36 +08:00
cnc
ce0605d20d 按提示词结论统一 wasm 源集依据锁 2026-06-02 13:58:02 +08:00
cnc
67154a70fa 按提示词结论补 wasm 探针源依据锁 2026-06-02 13:46:52 +08:00
cnc
5c7093ba39 按提示词结论补 INI 命名参数桥 2026-06-02 13:30:54 +08:00
cnc
0e1e22a864 按提示词结论接入 INI 参数文件桥 2026-06-02 13:26:09 +08:00
cnc
cee1bf3a3f 按提示词结论补参数文件默认回落锁 2026-06-02 13:10:32 +08:00
430 changed files with 12697 additions and 252 deletions

View File

@@ -8,8 +8,8 @@ cd "$(dirname "$0")"
# src/emc/rs274ngc/gcodemodule.cc, interp_namedparams.cc, interp_o_word.cc, # src/emc/rs274ngc/gcodemodule.cc, interp_namedparams.cc, interp_o_word.cc,
# and the py*.cc module bindings; tooldata/native-backend blockers come from # and the py*.cc module bindings; tooldata/native-backend blockers come from
# src/emc/tooldata/tooldata_mmap.cc and tooldata_db.cc; native filesystem # 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 # coverage comes from LinuxCNC RS274 sources such as interp_o_word.cc
# interp_write.cc. # directory traversal and rs274ngc_pre.cc parameter-file replacement.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
@@ -17,6 +17,7 @@ LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest"
linuxcnc_root=$(cd "$linuxcnc_root" && pwd) linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
python_blockers=() python_blockers=()
python_compile_covered=()
core_python_sources=() core_python_sources=()
dlopen_blockers=() dlopen_blockers=()
core_dlopen_sources=() core_dlopen_sources=()
@@ -61,13 +62,27 @@ wasm_safe_shim_in_build() {
return 1 return 1
} }
python_compile_probe_covers() {
grep -F "$1" test-linuxcnc-wasm-python-c-api-symbols.sh >/dev/null
}
core_sources=() core_sources=()
blocked_sources=() blocked_sources=()
while IFS=: read -r group path note extra; do manifest_line_number=0
case "$group" in while IFS= read -r manifest_line || [[ -n "$manifest_line" ]]; do
manifest_line_number=$((manifest_line_number + 1))
case "$manifest_line" in
""|\#*) ""|\#*)
continue continue
;; ;;
esac
IFS=: read -r group path note extra <<<"$manifest_line"
if [[ -z "$group" || -z "$path" || -z "$note" || -n "${extra:-}" ]]; then
echo "bad manifest line $manifest_line_number: $manifest_line" >&2
exit 1
fi
case "$group" in
core) core)
core_sources+=("$path") core_sources+=("$path")
;; ;;
@@ -124,6 +139,9 @@ scan_source() {
if grep -Eq 'Python\.h|boost/python|pythonplugin|PyObject|PyInit_' "$full_path"; then if grep -Eq 'Python\.h|boost/python|pythonplugin|PyObject|PyInit_' "$full_path"; then
if [[ "$group" == "blocked" ]]; then if [[ "$group" == "blocked" ]]; then
python_blockers+=("$path") python_blockers+=("$path")
if python_compile_probe_covers "$path"; then
python_compile_covered+=("$path")
fi
else else
core_python_sources+=("$path") core_python_sources+=("$path")
fi fi
@@ -182,6 +200,7 @@ echo "blocked=$blocked_count"
echo echo
print_group "python" "${python_blockers[@]}" print_group "python" "${python_blockers[@]}"
print_group "python-compile-covered" "${python_compile_covered[@]}"
print_group "dlopen" "${dlopen_blockers[@]}" print_group "dlopen" "${dlopen_blockers[@]}"
print_group "tooldata" "${tooldata_blockers[@]}" print_group "tooldata" "${tooldata_blockers[@]}"
print_group "tooldata-users" "${shimmed_tooldata_users[@]}" print_group "tooldata-users" "${shimmed_tooldata_users[@]}"

View File

@@ -3,6 +3,10 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: native bridge object caches compile only project
# canon/kinematics bridge sources that wrap LinuxCNC src/emc/nml_intf/canon.hh
# and src/emc/kinematics implementations; G-code behavior remains in
# LinuxCNC RS274 objects linked by the caller.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
cxx=${CXX:-g++} cxx=${CXX:-g++}
if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: link probe modes reuse wasm-safe objects built from
# linuxcnc-rs274-wasm-source-files.txt and add only a narrow probe main that
# asserts LinuxCNC rs274ngc_pre/runtime symbols stay linked.
usage() { usage() {
echo "usage: $0 --mode rs274ngc-pre|runtime [manifest]" >&2 echo "usage: $0 --mode rs274ngc-pre|runtime [manifest]" >&2
} }
@@ -82,6 +85,9 @@ target_name=rs274ngc_pre_probe
common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh --profile core20-sections) common_flag_output=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-common-cxxflags.sh --profile core20-sections)
mapfile -t common_flags <<<"$common_flag_output" mapfile -t common_flags <<<"$common_flag_output"
case "$mode" in case "$mode" in
rs274ngc-pre)
grep -F 'LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides' "$main_source" >/dev/null
;;
runtime) runtime)
object_mode=runtime object_mode=runtime
main_source=core/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc main_source=core/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc

View File

@@ -6,8 +6,9 @@ cd "$(dirname "$0")"
# LinuxCNC source basis: object probes compile the core entries selected from # LinuxCNC source basis: object probes compile the core entries selected from
# linuxcnc-rs274-wasm-source-files.txt, including src/emc/rs274ngc, # linuxcnc-rs274-wasm-source-files.txt, including src/emc/rs274ngc,
# src/emc/tooldata/tooldata_common.cc, src/emc/ini/inifile.cc, and # src/emc/tooldata/tooldata_common.cc, src/emc/ini/inifile.cc, and
# src/emc/nml_intf/emcops.cc, together with wasm-safe shims that replace the # src/emc/nml_intf/emcops.cc, together with CMake-listed wasm-safe project
# manifest's blocked Python, dlopen, tooldata mmap, HAL, and runtime edges. # bridge sources and shims that replace the manifest's blocked Python, dlopen,
# tooldata mmap, HAL, and runtime edges.
usage() { usage() {
echo "usage: $0 --mode source-objects|rs274ngc-pre|runtime [manifest]" >&2 echo "usage: $0 --mode source-objects|rs274ngc-pre|runtime [manifest]" >&2
} }
@@ -99,6 +100,9 @@ case "$mode" in
;; ;;
esac esac
grep -F 'LinuxCNC source basis: wasm-safe project probe sources are the thin bridge' list-linuxcnc-wasm-safe-project-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: wasm-safe shim probe sources are limited to the' list-linuxcnc-wasm-safe-shims.sh >/dev/null
sources=() sources=()
shim_output=$(./list-linuxcnc-wasm-safe-shims.sh) shim_output=$(./list-linuxcnc-wasm-safe-shims.sh)
mapfile -t shim_sources <<<"$shim_output" mapfile -t shim_sources <<<"$shim_output"
@@ -106,21 +110,13 @@ sources+=("${shim_sources[@]}")
case "$mode" in case "$mode" in
source-objects) source-objects)
;; ;;
rs274ngc-pre) rs274ngc-pre|runtime)
project_source_output=$(./list-linuxcnc-wasm-safe-project-sources.sh) project_source_output=$(./list-linuxcnc-wasm-safe-project-sources.sh)
if [[ -n "$project_source_output" ]]; then if [[ -n "$project_source_output" ]]; then
mapfile -t project_sources <<<"$project_source_output" mapfile -t project_sources <<<"$project_source_output"
sources+=("${project_sources[@]}") sources+=("${project_sources[@]}")
fi fi
;; ;;
runtime)
core_source_output=$(./list-cmake-set-sources.sh cnc_sim_core_sources core/)
mapfile -t core_sources <<<"$core_source_output"
sources+=("${core_sources[@]}")
backend_source_output=$(./list-cmake-set-sources.sh cnc_sim_linuxcnc_rs274_backend_sources core/)
mapfile -t backend_sources <<<"$backend_source_output"
sources+=("${backend_sources[@]}")
;;
esac esac
source_output=$(CNC_SIM_PREFLIGHT_CACHE_DIR="$preflight_cache_dir" LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full) source_output=$(CNC_SIM_PREFLIGHT_CACHE_DIR="$preflight_cache_dir" LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full)
mapfile -t linuxcnc_sources <<<"$source_output" mapfile -t linuxcnc_sources <<<"$source_output"

View File

@@ -3,8 +3,11 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: standalone probe modes either compile LinuxCNC
# rs274/tooldata sources directly or link wasm-safe shims whose source basis is
# checked against LinuxCNC source comments before building.
usage() { 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= mode=
@@ -30,7 +33,7 @@ while [[ "$#" -gt 0 ]]; do
esac esac
done done
case "$mode" in 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 usage
@@ -111,17 +114,25 @@ case "$mode" in
) )
target_name=interp_base_probe 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) 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+=( sources+=(
"$python_plugin_shim" "$python_plugin_shim"
core/wasm_shims/pythonplugin/python_plugin_probe_main.cc "$probe_source"
) )
target_name=python_plugin_probe
;; ;;
rs274ngc-pre-object) rs274ngc-pre-object)
rs274ngc_pre_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/rs274ngc/rs274ngc_pre.cc) rs274ngc_pre_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/rs274ngc/rs274ngc_pre.cc)
grep -F 'core:src/emc/rs274ngc/rs274ngc_pre.cc:Interp implementation covered by rs274ngc_pre link probe' linuxcnc-rs274-wasm-source-files.txt >/dev/null
sources+=("$rs274ngc_pre_source") sources+=("$rs274ngc_pre_source")
target_name=rs274ngc_pre.o target_name=rs274ngc_pre.o
;; ;;

View File

@@ -3,6 +3,10 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: the wasm build preflights and compiles the
# linuxcnc-rs274-wasm-source-files.txt manifest entries from
# src/emc/rs274ngc/tooldata plus source-basis-checked shims before copying the
# browser artifacts; it must not add independent interpreter behavior.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
build_jobs=${CNC_SIM_BUILD_JOBS:-8} build_jobs=${CNC_SIM_BUILD_JOBS:-8}
@@ -22,6 +26,18 @@ fi
preflight_cache_dir=build/wasm-preflight-cache preflight_cache_dir=build/wasm-preflight-cache
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh --cache-dir "$preflight_cache_dir" "$manifest" 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") manifest=$(cd "$(dirname "$manifest")" && pwd)/$(basename "$manifest")
linuxcnc_root=$(cd "$linuxcnc_root" && pwd) linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
default_manifest="$PWD/linuxcnc-rs274-wasm-source-files.txt" default_manifest="$PWD/linuxcnc-rs274-wasm-source-files.txt"
@@ -128,6 +144,7 @@ run_parallel_wasm_probe "LinuxCNC wasm interp_base link probe" ./test-linuxcnc-w
run_parallel_wasm_probe "LinuxCNC wasm interp_find link probe" ./test-linuxcnc-wasm-interp-find-link.sh run_parallel_wasm_probe "LinuxCNC wasm interp_find link probe" ./test-linuxcnc-wasm-interp-find-link.sh
run_parallel_wasm_probe "LinuxCNC wasm python_plugin link probe" ./test-linuxcnc-wasm-python-plugin-link.sh run_parallel_wasm_probe "LinuxCNC wasm python_plugin link probe" ./test-linuxcnc-wasm-python-plugin-link.sh
run_parallel_wasm_probe "LinuxCNC wasm Python C API symbol probe" ./test-linuxcnc-wasm-python-c-api-symbols.sh run_parallel_wasm_probe "LinuxCNC wasm Python C API symbol probe" ./test-linuxcnc-wasm-python-c-api-symbols.sh
run_parallel_wasm_probe "LinuxCNC wasm Python binding link probe" ./test-linuxcnc-wasm-python-binding-link.sh
run_parallel_wasm_probe "LinuxCNC wasm rs274ngc_pre object probe" ./test-linuxcnc-wasm-rs274ngc-pre-object.sh run_parallel_wasm_probe "LinuxCNC wasm rs274ngc_pre object probe" ./test-linuxcnc-wasm-rs274ngc-pre-object.sh
wait_parallel_wasm_probes wait_parallel_wasm_probes
@@ -164,7 +181,7 @@ wasm_build_next_signature=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_build.signature
printf 'COMPILER_LAUNCHER=%s\n' "$cmake_compiler_launcher_mode" printf 'COMPILER_LAUNCHER=%s\n' "$cmake_compiler_launcher_mode"
printf 'CMAKE_LISTS=' printf 'CMAKE_LISTS='
sha256sum core/CMakeLists.txt sha256sum core/CMakeLists.txt
printf 'CONFIGURE_RULE_VERSION=1\n' printf 'CONFIGURE_RULE_VERSION=2\n'
} > "$wasm_build_next_signature" } > "$wasm_build_next_signature"
configure_wasm=1 configure_wasm=1
if [[ ! -f "$wasm_build_signature" ]] || ! cmp -s "$wasm_build_signature" "$wasm_build_next_signature"; then if [[ ! -f "$wasm_build_signature" ]] || ! cmp -s "$wasm_build_signature" "$wasm_build_next_signature"; then
@@ -179,6 +196,7 @@ emcmake cmake -S core -B build/wasm \
-DCNC_SIM_LINUXCNC_ROOT="$linuxcnc_root" \ -DCNC_SIM_LINUXCNC_ROOT="$linuxcnc_root" \
-DCNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST="$manifest" \ -DCNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST="$manifest" \
-DCNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON \ -DCNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON \
-DCNC_SIM_ENABLE_SMOKE_BACKEND=OFF \
"${cmake_compiler_launcher_args[@]}" "${cmake_compiler_launcher_args[@]}"
fi fi
mv "$wasm_build_next_signature" "$wasm_build_signature" mv "$wasm_build_next_signature" "$wasm_build_signature"

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

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: the cached input checker keys check-linuxcnc-inputs.sh
# results by manifest contents and LinuxCNC source mtimes so source-backed
# rs274/kinematics coverage is not silently reused after upstream changes.
cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-} cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-}
check_args=("$@") check_args=("$@")
if [[ "${1:-}" == "--cache-dir" ]]; then if [[ "${1:-}" == "--cache-dir" ]]; then

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: manifest validation checks LinuxCNC-root relative
# sources and completeness against src/emc/rs274ngc, src/emc/kinematics,
# src/libnml/posemath, and configs/sim without interpreting CNC behavior.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-} manifest=${1:-}
require_complete=${2:-} require_complete=${2:-}
@@ -458,6 +461,34 @@ if [[ -n "$manifest" && "$require_complete" == "--require-kinematics-complete" ]
exit 1 exit 1
fi fi
generated_component_manpage_sources=()
for source in "${manifest_generated_sources[@]}"; do
case "$source" in
src/objects/hal/components/*.c)
component_name=$(basename "$source" .c)
manpage_source="src/objects/man/man9/$component_name.9.adoc"
if [[ -f "$linuxcnc_root/$manpage_source" ]]; then
generated_component_manpage_sources+=("$manpage_source")
fi
;;
esac
done
mapfile -t generated_component_manpage_sources < <(
printf '%s\n' "${generated_component_manpage_sources[@]}" | sed '/^$/d' | sort -u
)
missing_generated_component_manpage_sources=()
for source in "${generated_component_manpage_sources[@]}"; do
if ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then
missing_generated_component_manpage_sources+=("$source")
fi
done
if ((${#missing_generated_component_manpage_sources[@]} > 0)); then
echo "manifest does not cover all LinuxCNC generated component manpage sources adjacent to generated kinematics sources: $manifest" >&2
printf 'missing generated component manpage source: %s\n' "${missing_generated_component_manpage_sources[@]}" >&2
exit 1
fi
missing_m428_m430_ini_remap_sources=() missing_m428_m430_ini_remap_sources=()
unresolved_m428_m430_ini_remaps=() unresolved_m428_m430_ini_remaps=()
for source in "${manifest_m428_m430_ini_sources[@]}"; do for source in "${manifest_m428_m430_ini_sources[@]}"; do
@@ -868,6 +899,57 @@ if [[ -n "$manifest" && "$require_complete" == "--require-kinematics-complete" ]
fi fi
} }
extract_waited_loadusr_programs() {
# Source basis: LinuxCNC src/hal/utils/halcmd_commands.cc
# do_loadusr_cmd() parses loadusr options with getopt("+wWin:"),
# and its help text plus src/hal/utils/halcmd_completion.c list both
# "-W" and "-Wn" wait forms.
awk '
function emit_waited_program(fields, field_count, start_index, i, token, waiting) {
waiting = 0
for (i = start_index + 1; i <= field_count; i++) {
token = fields[i]
if (token == "-W") {
waiting = 1
continue
}
if (token == "-Wn") {
waiting = 1
i++
continue
}
if (token == "-n") {
i++
continue
}
if (token ~ /^-Wn.+/) {
waiting = 1
continue
}
if (token ~ /^-n.+/) continue
if (token ~ /^-[wWi]+$/) {
if (token ~ /W/) waiting = 1
continue
}
if (token ~ /^-/) continue
if (waiting && token != "") print token
return
}
}
{
line = $0
sub(/[[:space:]]*[#;].*$/, "", line)
field_count = split(line, fields, /[[:space:]]+/)
for (i = 1; i <= field_count; i++) {
if (fields[i] == "loadusr") {
emit_waited_program(fields, field_count, i)
next
}
}
}
' "$1"
}
m428_m430_vismach_loadusr_sources=() m428_m430_vismach_loadusr_sources=()
for source in "${manifest_m428_m430_ini_sources[@]}"; do for source in "${manifest_m428_m430_ini_sources[@]}"; do
while IFS= read -r command_name; do while IFS= read -r command_name; do
@@ -875,20 +957,7 @@ if [[ -n "$manifest" && "$require_complete" == "--require-kinematics-complete" ]
while IFS= read -r vismach_source; do while IFS= read -r vismach_source; do
[[ -n "$vismach_source" ]] && m428_m430_vismach_loadusr_sources+=("$vismach_source") [[ -n "$vismach_source" ]] && m428_m430_vismach_loadusr_sources+=("$vismach_source")
done < <(resolve_vismach_loadusr_source "$command_name") done < <(resolve_vismach_loadusr_source "$command_name")
done < <( done < <(extract_waited_loadusr_programs "$linuxcnc_root/$source")
awk '
BEGIN { IGNORECASE = 1 }
/^[[:space:]]*HALCMD[[:space:]]*=/ && /loadusr[[:space:]]/ {
for (i = 1; i <= NF; i++) {
if ($i == "-W" && (i + 1) <= NF) {
value = $(i + 1)
sub(/[[:space:]#;].*/, "", value)
if (value != "") print value
}
}
}
' "$linuxcnc_root/$source"
)
done done
for source in "${m428_m430_config_hal_sources[@]}"; do for source in "${m428_m430_config_hal_sources[@]}"; do
while IFS= read -r command_name; do while IFS= read -r command_name; do
@@ -896,19 +965,7 @@ if [[ -n "$manifest" && "$require_complete" == "--require-kinematics-complete" ]
while IFS= read -r vismach_source; do while IFS= read -r vismach_source; do
[[ -n "$vismach_source" ]] && m428_m430_vismach_loadusr_sources+=("$vismach_source") [[ -n "$vismach_source" ]] && m428_m430_vismach_loadusr_sources+=("$vismach_source")
done < <(resolve_vismach_loadusr_source "$command_name") done < <(resolve_vismach_loadusr_source "$command_name")
done < <( done < <(extract_waited_loadusr_programs "$linuxcnc_root/$source")
awk '
/^[[:space:]]*loadusr[[:space:]]/ {
for (i = 1; i <= NF; i++) {
if ($i == "-W" && (i + 1) <= NF) {
value = $(i + 1)
sub(/[[:space:]#;].*/, "", value)
if (value != "") print value
}
}
}
' "$linuxcnc_root/$source"
)
done done
mapfile -t m428_m430_vismach_loadusr_sources < <( mapfile -t m428_m430_vismach_loadusr_sources < <(
printf '%s\n' "${m428_m430_vismach_loadusr_sources[@]}" | sed '/^$/d' | sort -u printf '%s\n' "${m428_m430_vismach_loadusr_sources[@]}" | sed '/^$/d' | sort -u
@@ -1131,6 +1188,37 @@ if [[ -n "$manifest" && "$require_complete" == "--require-kinematics-complete" ]
printf '%s\n' "${unresolved_m428_m430_config_tooldata_sources[@]}" | sed '/^$/d' | sort -u printf '%s\n' "${unresolved_m428_m430_config_tooldata_sources[@]}" | sed '/^$/d' | sort -u
) )
# Source basis: these LinuxCNC M428/M429/M430 INI files name TOOL_TABLE
# entries that are absent from the current LinuxCNC source tree. Keep the
# unresolved set explicit so new missing tool tables do not pass silently.
expected_unresolved_m428_m430_config_tooldata_sources=(
"configs/sim/axis/vismach/hexapod-sim/hexapod.ini TOOL_TABLE=hexapod.tbl"
"configs/sim/axis/vismach/scara/scara.ini TOOL_TABLE=scara.tbl"
"configs/sim/qtvcp_screens/non-trivial/scara/scara.ini TOOL_TABLE=scara.tbl"
)
unexpected_unresolved_m428_m430_config_tooldata_sources=()
for source in "${unresolved_m428_m430_config_tooldata_sources[@]}"; do
if ! printf '%s\n' "${expected_unresolved_m428_m430_config_tooldata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then
unexpected_unresolved_m428_m430_config_tooldata_sources+=("$source")
fi
done
missing_expected_unresolved_m428_m430_config_tooldata_sources=()
for source in "${expected_unresolved_m428_m430_config_tooldata_sources[@]}"; do
if ! printf '%s\n' "${unresolved_m428_m430_config_tooldata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then
missing_expected_unresolved_m428_m430_config_tooldata_sources+=("$source")
fi
done
if ((${#unexpected_unresolved_m428_m430_config_tooldata_sources[@]} > 0)); then
echo "LinuxCNC M428/M429/M430 INI TOOL_TABLE files have new unresolved sources: $manifest" >&2
printf 'unexpected unresolved M428/M429/M430 tool table source: %s\n' "${unexpected_unresolved_m428_m430_config_tooldata_sources[@]}" >&2
exit 1
fi
if ((${#missing_expected_unresolved_m428_m430_config_tooldata_sources[@]} > 0)); then
echo "LinuxCNC M428/M429/M430 INI TOOL_TABLE unresolved source set changed: $manifest" >&2
printf 'missing expected unresolved M428/M429/M430 tool table source: %s\n' "${missing_expected_unresolved_m428_m430_config_tooldata_sources[@]}" >&2
exit 1
fi
missing_m428_m430_config_tooldata_sources=() missing_m428_m430_config_tooldata_sources=()
for source in "${m428_m430_config_tooldata_sources[@]}"; do for source in "${m428_m430_config_tooldata_sources[@]}"; do
if ! printf '%s\n' "${manifest_tooldata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then if ! printf '%s\n' "${manifest_tooldata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then

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

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: cached switchkins/remap validation hashes the LinuxCNC
# kinematics manifest, referenced configs/remaps, generator script, and bridge
# outputs before reusing table-check results.
cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-} cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-}
check_args=("$@") check_args=("$@")
if [[ "${1:-}" == "--cache-dir" ]]; then if [[ "${1:-}" == "--cache-dir" ]]; then
@@ -89,6 +92,10 @@ if ! {
sha256sum check-linuxcnc-switchkins-remap-table-cached.sh sha256sum check-linuxcnc-switchkins-remap-table-cached.sh
printf 'CHECK_SCRIPT=' printf 'CHECK_SCRIPT='
sha256sum check-linuxcnc-switchkins-remap-table.sh sha256sum check-linuxcnc-switchkins-remap-table.sh
printf 'INPUT_CACHED_SCRIPT='
sha256sum check-linuxcnc-inputs-cached.sh
printf 'INPUT_CHECK_SCRIPT='
sha256sum check-linuxcnc-inputs.sh
printf 'GENERATOR_SCRIPT=' printf 'GENERATOR_SCRIPT='
sha256sum generate-linuxcnc-switchkins-remap-table.sh sha256sum generate-linuxcnc-switchkins-remap-table.sh
printf 'LINUXCNC_ROOT=%s\n' "$linuxcnc_root" printf 'LINUXCNC_ROOT=%s\n' "$linuxcnc_root"

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: switchkins/remap table validation derives M428/M429/M430
# cases from LinuxCNC configs/sim INI/HAL files, remap_subs/*.ngc #<kinstype>
# assignments, and src/emc/kinematics switchkins sources.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-kinematics-source-files.txt} manifest=${1:-linuxcnc-kinematics-source-files.txt}
@@ -32,6 +35,149 @@ if [[ ! -f "$manifest" ]]; then
echo "missing kinematics manifest: $manifest" >&2 echo "missing kinematics manifest: $manifest" >&2
exit 1 exit 1
fi fi
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest" --require-kinematics-complete
validate_manifest_switchkins_complete() {
local manifest_file=$1
if command -v python3 >/dev/null 2>&1; then
if ! python3 - "$linuxcnc_root" "$manifest_file" <<'PY'
import os
import re
import sys
linuxcnc_root, manifest_file = sys.argv[1:]
sim_root = os.path.join(linuxcnc_root, "configs", "sim")
remap_ini_re = re.compile(r"REMAP\s*=\s*M(428|429|430)(?:\D|$)", re.I)
switchkins_remap_names = {"428remap.ngc", "429remap.ngc", "430remap.ngc"}
manifest_config_paths = set()
manifest_remap_paths = set()
with open(manifest_file, encoding="utf-8") as manifest:
for line in manifest:
parts = line.rstrip("\n").split(":", 2)
if len(parts) < 2:
continue
group, source_path = parts[:2]
if group == "config" and source_path.endswith(".ini"):
with open(os.path.join(linuxcnc_root, source_path), encoding="utf-8") as handle:
if remap_ini_re.search(handle.read()):
manifest_config_paths.add(source_path)
elif group == "remap":
manifest_remap_paths.add(source_path)
linuxcnc_config_paths = set()
linuxcnc_remap_paths = set()
for current_root, _dirs, files in os.walk(sim_root):
for filename in files:
source = os.path.join(current_root, filename)
source_path = os.path.relpath(source, linuxcnc_root)
if filename.endswith(".ini"):
with open(source, encoding="utf-8", errors="ignore") as handle:
if remap_ini_re.search(handle.read()):
linuxcnc_config_paths.add(source_path)
elif filename in switchkins_remap_names:
linuxcnc_remap_paths.add(source_path)
missing_configs = sorted(linuxcnc_config_paths - manifest_config_paths)
extra_configs = sorted(manifest_config_paths - linuxcnc_config_paths)
missing_remaps = sorted(linuxcnc_remap_paths - manifest_remap_paths)
extra_remaps = sorted(manifest_remap_paths - linuxcnc_remap_paths)
for source_path in missing_configs:
print(f"missing LinuxCNC switchkins REMAP INI in manifest: {source_path}", file=sys.stderr)
for source_path in extra_configs:
print(f"unexpected LinuxCNC switchkins REMAP INI in manifest: {source_path}", file=sys.stderr)
for source_path in missing_remaps:
print(f"missing LinuxCNC switchkins remap source in manifest: {source_path}", file=sys.stderr)
for source_path in extra_remaps:
print(f"unexpected LinuxCNC switchkins remap source in manifest: {source_path}", file=sys.stderr)
if missing_configs or extra_configs or missing_remaps or extra_remaps:
raise SystemExit(1)
PY
then
exit 1
fi
elif command -v node >/dev/null 2>&1; then
if ! node - "$linuxcnc_root" "$manifest_file" <<'JS'
const fs = require("fs");
const path = require("path");
const [linuxcncRoot, manifestFile] = process.argv.slice(2);
const simRoot = path.join(linuxcncRoot, "configs", "sim");
const remapIniRe = /REMAP\s*=\s*M(428|429|430)(?:\D|$)/i;
const switchkinsRemapNames = new Set(["428remap.ngc", "429remap.ngc", "430remap.ngc"]);
const manifestConfigPaths = new Set();
const manifestRemapPaths = new Set();
for (const line of fs.readFileSync(manifestFile, "utf8").split(/\r?\n/)) {
const parts = line.split(":", 3);
if (parts.length < 2) {
continue;
}
const [group, sourcePath] = parts;
if (group === "config" && sourcePath.endsWith(".ini")) {
const text = fs.readFileSync(path.join(linuxcncRoot, sourcePath), "utf8");
if (remapIniRe.test(text)) {
manifestConfigPaths.add(sourcePath);
}
} else if (group === "remap") {
manifestRemapPaths.add(sourcePath);
}
}
function walkFiles(root) {
const files = [];
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
const fullPath = path.join(root, entry.name);
if (entry.isDirectory()) {
files.push(...walkFiles(fullPath));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files;
}
const linuxcncConfigPaths = new Set();
const linuxcncRemapPaths = new Set();
for (const source of walkFiles(simRoot)) {
const sourcePath = path.relative(linuxcncRoot, source);
const basename = path.basename(source);
if (basename.endsWith(".ini")) {
const text = fs.readFileSync(source, "utf8");
if (remapIniRe.test(text)) {
linuxcncConfigPaths.add(sourcePath);
}
} else if (switchkinsRemapNames.has(basename)) {
linuxcncRemapPaths.add(sourcePath);
}
}
const missingConfigs = [...linuxcncConfigPaths].filter((sourcePath) => !manifestConfigPaths.has(sourcePath)).sort();
const extraConfigs = [...manifestConfigPaths].filter((sourcePath) => !linuxcncConfigPaths.has(sourcePath)).sort();
const missingRemaps = [...linuxcncRemapPaths].filter((sourcePath) => !manifestRemapPaths.has(sourcePath)).sort();
const extraRemaps = [...manifestRemapPaths].filter((sourcePath) => !linuxcncRemapPaths.has(sourcePath)).sort();
for (const sourcePath of missingConfigs) {
console.error(`missing LinuxCNC switchkins REMAP INI in manifest: ${sourcePath}`);
}
for (const sourcePath of extraConfigs) {
console.error(`unexpected LinuxCNC switchkins REMAP INI in manifest: ${sourcePath}`);
}
for (const sourcePath of missingRemaps) {
console.error(`missing LinuxCNC switchkins remap source in manifest: ${sourcePath}`);
}
for (const sourcePath of extraRemaps) {
console.error(`unexpected LinuxCNC switchkins remap source in manifest: ${sourcePath}`);
}
if (missingConfigs.length || extraConfigs.length || missingRemaps.length || extraRemaps.length) {
process.exit(1);
}
JS
then
exit 1
fi
else
echo "missing python3 or node for switchkins remap manifest completeness validation" >&2
exit 1
fi
}
validate_manifest_switchkins_complete "$manifest"
validate_manifest_remap_kinstypes() { validate_manifest_remap_kinstypes() {
local manifest_file=$1 local manifest_file=$1
@@ -210,28 +356,64 @@ validate_generator_cli_contract() {
local cli_dir local cli_dir
local cli_manifest local cli_manifest
local cli_manifest_with_config_ngc local cli_manifest_with_config_ngc
local cli_manifest_with_config_tbl
local cli_manifest_with_duplicate_ini local cli_manifest_with_duplicate_ini
local cli_manifest_with_duplicate_remap local cli_manifest_with_duplicate_remap
local cli_manifest_with_duplicate_tooldata
local cli_manifest_with_unused_hal local cli_manifest_with_unused_hal
local cli_manifest_with_unused_postgui local cli_manifest_with_unused_postgui
local cli_manifest_with_unused_remap local cli_manifest_with_unused_remap
local cli_manifest_with_unused_tooldata
local cli_manifest_without_halfile_loadusr_asset
local cli_melfa_manifest
local cli_melfa_without_halcmd_loadusr_asset
local cli_trsrn_manifest
local cli_trsrn_without_g531_asset
local cli_trsrn_without_g69_asset
local cli_trsrn_without_halcmd_asset
local cli_trsrn_without_halcmd_import_asset
local cli_trsrn_without_on_abort_asset
local cli_trsrn_without_python_import_asset
local cli_trsrn_without_python_toplevel_asset
local cli_manifest_without_ini local cli_manifest_without_ini
local cli_manifest_without_hal local cli_manifest_without_hal
local cli_manifest_without_postgui local cli_manifest_without_postgui
local cli_manifest_without_remap 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") cli_dir=$(mktemp -d "${TMPDIR:-/tmp}/linuxcnc_switchkins_remap_cli.XXXXXX")
trap 'rm -rf "$cli_dir"' EXIT trap 'rm -rf "$cli_dir"' EXIT
cli_manifest="$cli_dir/bridgemill-manifest.txt" cli_manifest="$cli_dir/bridgemill-manifest.txt"
cli_manifest_with_config_ngc="$cli_dir/bridgemill-manifest-with-config-ngc.txt" cli_manifest_with_config_ngc="$cli_dir/bridgemill-manifest-with-config-ngc.txt"
cli_manifest_with_config_tbl="$cli_dir/bridgemill-manifest-with-config-tbl.txt"
cli_manifest_with_duplicate_ini="$cli_dir/bridgemill-manifest-with-duplicate-ini.txt" cli_manifest_with_duplicate_ini="$cli_dir/bridgemill-manifest-with-duplicate-ini.txt"
cli_manifest_with_duplicate_remap="$cli_dir/bridgemill-manifest-with-duplicate-remap.txt" cli_manifest_with_duplicate_remap="$cli_dir/bridgemill-manifest-with-duplicate-remap.txt"
cli_manifest_with_duplicate_tooldata="$cli_dir/bridgemill-manifest-with-duplicate-tooldata.txt"
cli_manifest_with_unused_hal="$cli_dir/bridgemill-manifest-with-unused-hal.txt" cli_manifest_with_unused_hal="$cli_dir/bridgemill-manifest-with-unused-hal.txt"
cli_manifest_with_unused_postgui="$cli_dir/bridgemill-manifest-with-unused-postgui.txt" cli_manifest_with_unused_postgui="$cli_dir/bridgemill-manifest-with-unused-postgui.txt"
cli_manifest_with_unused_remap="$cli_dir/bridgemill-manifest-with-unused-remap.txt" cli_manifest_with_unused_remap="$cli_dir/bridgemill-manifest-with-unused-remap.txt"
cli_manifest_with_unused_tooldata="$cli_dir/bridgemill-manifest-with-unused-tooldata.txt"
cli_manifest_without_halfile_loadusr_asset="$cli_dir/bridgemill-manifest-without-halfile-loadusr-asset.txt"
cli_melfa_manifest="$cli_dir/melfa-manifest.txt"
cli_melfa_without_halcmd_loadusr_asset="$cli_dir/melfa-manifest-without-halcmd-loadusr-asset.txt"
cli_trsrn_manifest="$cli_dir/trsrn-manifest.txt"
cli_trsrn_without_g531_asset="$cli_dir/trsrn-manifest-without-g531-asset.txt"
cli_trsrn_without_g69_asset="$cli_dir/trsrn-manifest-without-g69-asset.txt"
cli_trsrn_without_halcmd_asset="$cli_dir/trsrn-manifest-without-halcmd-asset.txt"
cli_trsrn_without_halcmd_import_asset="$cli_dir/trsrn-manifest-without-halcmd-import-asset.txt"
cli_trsrn_without_on_abort_asset="$cli_dir/trsrn-manifest-without-on-abort-asset.txt"
cli_trsrn_without_python_import_asset="$cli_dir/trsrn-manifest-without-python-import-asset.txt"
cli_trsrn_without_python_toplevel_asset="$cli_dir/trsrn-manifest-without-python-toplevel-asset.txt"
cli_manifest_without_ini="$cli_dir/bridgemill-manifest-without-ini.txt" cli_manifest_without_ini="$cli_dir/bridgemill-manifest-without-ini.txt"
cli_manifest_without_hal="$cli_dir/bridgemill-manifest-without-hal.txt" cli_manifest_without_hal="$cli_dir/bridgemill-manifest-without-hal.txt"
cli_manifest_without_postgui="$cli_dir/bridgemill-manifest-without-postgui.txt" cli_manifest_without_postgui="$cli_dir/bridgemill-manifest-without-postgui.txt"
cli_manifest_without_remap="$cli_dir/bridgemill-manifest-without-remap.txt" 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" ' if ! awk -F: -v manifest="$manifest_file" '
function require(key) { function require(key) {
@@ -245,6 +427,8 @@ validate_generator_cli_contract() {
require("remap:configs/sim/axis/vismach/5axis/bridgemill/remap_subs/428remap.ngc") require("remap:configs/sim/axis/vismach/5axis/bridgemill/remap_subs/428remap.ngc")
require("remap:configs/sim/axis/vismach/5axis/bridgemill/remap_subs/429remap.ngc") require("remap:configs/sim/axis/vismach/5axis/bridgemill/remap_subs/429remap.ngc")
require("remap:configs/sim/axis/vismach/5axis/bridgemill/remap_subs/430remap.ngc") require("remap:configs/sim/axis/vismach/5axis/bridgemill/remap_subs/430remap.ngc")
require("tooldata:configs/sim/axis/vismach/5axis/bridgemill/5axis.tbl")
require("asset:src/hal/user_comps/vismach/5axisgui.py")
} }
{ {
key = $1 ":" $2 key = $1 ":" $2
@@ -282,6 +466,15 @@ validate_generator_cli_contract() {
echo "switchkins remap generator accepted LinuxCNC remap source listed as config" >&2 echo "switchkins remap generator accepted LinuxCNC remap source listed as config" >&2
exit 1 exit 1
fi fi
cp "$cli_manifest" "$cli_manifest_with_config_tbl"
grep -F \
'tooldata:configs/sim/axis/vismach/5axis/bridgemill/5axis.tbl:' \
"$cli_manifest" | sed 's/^tooldata:/config:/' >>"$cli_manifest_with_config_tbl"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_manifest_with_config_tbl" >/dev/null 2>&1; then
echo "switchkins remap generator accepted LinuxCNC TOOL_TABLE source listed as config" >&2
exit 1
fi
cp "$cli_manifest" "$cli_manifest_with_duplicate_ini" cp "$cli_manifest" "$cli_manifest_with_duplicate_ini"
grep -F \ grep -F \
'config:configs/sim/axis/vismach/5axis/bridgemill/5axis.ini:' \ 'config:configs/sim/axis/vismach/5axis/bridgemill/5axis.ini:' \
@@ -300,6 +493,15 @@ validate_generator_cli_contract() {
echo "switchkins remap generator accepted duplicate LinuxCNC M428 remap source" >&2 echo "switchkins remap generator accepted duplicate LinuxCNC M428 remap source" >&2
exit 1 exit 1
fi fi
cp "$cli_manifest" "$cli_manifest_with_duplicate_tooldata"
grep -F \
'tooldata:configs/sim/axis/vismach/5axis/bridgemill/5axis.tbl:' \
"$cli_manifest" >>"$cli_manifest_with_duplicate_tooldata"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_manifest_with_duplicate_tooldata" >/dev/null 2>&1; then
echo "switchkins remap generator accepted duplicate LinuxCNC TOOL_TABLE source" >&2
exit 1
fi
grep -Fv \ grep -Fv \
'config:configs/sim/axis/vismach/5axis/bridgemill/5axis.ini:' \ 'config:configs/sim/axis/vismach/5axis/bridgemill/5axis.ini:' \
"$cli_manifest" >"$cli_manifest_without_ini" "$cli_manifest" >"$cli_manifest_without_ini"
@@ -332,6 +534,72 @@ validate_generator_cli_contract() {
echo "switchkins remap generator accepted manifest missing LinuxCNC M430 remap source" >&2 echo "switchkins remap generator accepted manifest missing LinuxCNC M430 remap source" >&2
exit 1 exit 1
fi fi
grep -Fv \
'tooldata:configs/sim/axis/vismach/5axis/bridgemill/5axis.tbl:' \
"$cli_manifest" >"$cli_manifest_without_tooldata"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_manifest_without_tooldata" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC TOOL_TABLE source" >&2
exit 1
fi
grep -Fv \
'asset:src/hal/user_comps/vismach/5axisgui.py:' \
"$cli_manifest" >"$cli_manifest_without_halfile_loadusr_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_manifest_without_halfile_loadusr_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC HALFILE loadusr asset source" >&2
exit 1
fi
if ! awk -F: -v manifest="$manifest_file" '
function require(key) {
expected[++expected_count] = key
expected_keys[key] = 1
}
BEGIN {
require("config:configs/sim/axis/vismach/melfa-sim/melfa.ini")
require("config:configs/sim/axis/vismach/melfa-sim/melfa_dh.hal")
require("config:configs/sim/axis/vismach/melfa-sim/melfa-postgui.hal")
require("remap:configs/sim/axis/vismach/melfa-sim/remap_subs/428remap.ngc")
require("remap:configs/sim/axis/vismach/melfa-sim/remap_subs/429remap.ngc")
require("remap:configs/sim/axis/vismach/melfa-sim/remap_subs/430remap.ngc")
require("tooldata:configs/sim/axis/vismach/melfa-sim/melfa.tbl")
require("asset:src/hal/user_comps/vismach/melfagui.py")
}
{
key = $1 ":" $2
if (key in expected_keys) {
if (key in manifest_line) {
print "duplicate melfa switchkins source in " manifest ": " key > "/dev/stderr"
duplicate = 1
}
manifest_line[key] = $0
}
}
END {
missing = 0
for (expected_index = 1; expected_index <= expected_count; expected_index++) {
key = expected[expected_index]
if (!(key in manifest_line)) {
print "melfa switchkins source is not listed in " manifest ": " key > "/dev/stderr"
missing = 1
continue
}
print manifest_line[key]
}
exit missing || duplicate
}
' "$manifest_file" >"$cli_melfa_manifest"; then
exit 1
fi
grep -Fv \
'asset:src/hal/user_comps/vismach/melfagui.py:' \
"$cli_melfa_manifest" >"$cli_melfa_without_halcmd_loadusr_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_melfa_without_halcmd_loadusr_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC HALCMD loadusr command asset source" >&2
exit 1
fi
cp "$cli_manifest" "$cli_manifest_with_unused_hal" cp "$cli_manifest" "$cli_manifest_with_unused_hal"
grep -F \ grep -F \
'config:configs/sim/axis/vismach/melfa-sim/melfa_dh.hal:' \ 'config:configs/sim/axis/vismach/melfa-sim/melfa_dh.hal:' \
@@ -359,6 +627,162 @@ validate_generator_cli_contract() {
echo "switchkins remap generator accepted unused LinuxCNC M428 remap source" >&2 echo "switchkins remap generator accepted unused LinuxCNC M428 remap source" >&2
exit 1 exit 1
fi fi
cp "$cli_manifest" "$cli_manifest_with_unused_tooldata"
grep -F \
'tooldata:configs/sim/axis/vismach/melfa-sim/melfa.tbl:' \
"$manifest_file" >>"$cli_manifest_with_unused_tooldata"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_manifest_with_unused_tooldata" >/dev/null 2>&1; then
echo "switchkins remap generator accepted unused LinuxCNC TOOL_TABLE source" >&2
exit 1
fi
if ! awk -F: -v manifest="$manifest_file" '
function require(key) {
expected[++expected_count] = key
expected_keys[key] = 1
}
BEGIN {
require("config:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini")
require("config:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn_postgui.hal")
require("config:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.ini")
require("config:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn_postgui.hal")
require("remap:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/428remap.ngc")
require("remap:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/429remap.ngc")
require("remap:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/430remap.ngc")
require("tooldata:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.tbl")
require("tooldata:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzbca-trsrn_twp/xyzbca-trsrn.tbl")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/toplevel.py")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/twp-helper-comp.py")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/util.py")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g531remap.ngc")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g533remap.ngc")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g536remap.ngc")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g69remap.ngc")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/on_abort_no_twp_reset.ngc")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/on_abort_with_twp_reset.ngc")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/vismach/twp_vismach.py")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/vismach/xyzacb-trsrn-gui.py")
require("asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/vismach/xyzbca-trsrn-gui.py")
}
{
key = $1 ":" $2
if (key in expected_keys) {
if (key in manifest_line) {
print "duplicate TRSRN switchkins source in " manifest ": " key > "/dev/stderr"
duplicate = 1
}
manifest_line[key] = $0
}
}
END {
missing = 0
for (expected_index = 1; expected_index <= expected_count; expected_index++) {
key = expected[expected_index]
if (!(key in manifest_line)) {
print "TRSRN switchkins source is not listed in " manifest ": " key > "/dev/stderr"
missing = 1
continue
}
print manifest_line[key]
}
exit missing || duplicate
}
' "$manifest_file" >"$cli_trsrn_manifest"; then
exit 1
fi
grep -Fv \
'asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/toplevel.py:' \
"$cli_trsrn_manifest" >"$cli_trsrn_without_python_toplevel_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_trsrn_without_python_toplevel_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC PYTHON TOPLEVEL asset source" >&2
exit 1
fi
grep -Fv \
'asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/util.py:' \
"$cli_trsrn_manifest" >"$cli_trsrn_without_python_import_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_trsrn_without_python_import_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC Python import asset source" >&2
exit 1
fi
grep -Fv \
'asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/twp-helper-comp.py:' \
"$cli_trsrn_manifest" >"$cli_trsrn_without_halcmd_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_trsrn_without_halcmd_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC HALCMD loadusr asset source" >&2
exit 1
fi
grep -Fv \
'asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/vismach/twp_vismach.py:' \
"$cli_trsrn_manifest" >"$cli_trsrn_without_halcmd_import_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_trsrn_without_halcmd_import_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC HALCMD Python import asset source" >&2
exit 1
fi
grep -Fv \
'asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g531remap.ngc:' \
"$cli_trsrn_manifest" >"$cli_trsrn_without_g531_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_trsrn_without_g531_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC G53.1 remap asset source" >&2
exit 1
fi
grep -Fv \
'asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/g69remap.ngc:' \
"$cli_trsrn_manifest" >"$cli_trsrn_without_g69_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_trsrn_without_g69_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC G69 remap asset source" >&2
exit 1
fi
grep -Fv \
'asset:configs/sim/axis/vismach/5axis/table-rotary_spindle-rotary-nutating/remap_subs/on_abort_no_twp_reset.ngc:' \
"$cli_trsrn_manifest" >"$cli_trsrn_without_on_abort_asset"
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
"$cli_trsrn_without_on_abort_asset" >/dev/null 2>&1; then
echo "switchkins remap generator accepted manifest missing LinuxCNC ON_ABORT_COMMAND asset source" >&2
exit 1
fi
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
--all-output-dir "$cli_dir/out with space" "$cli_manifest" >"$cli_output_whitespace_log" 2>&1; then
echo "switchkins remap generator accepted an output directory with whitespace" >&2
exit 1
fi
if ! grep -F "switchkins remap output directory must not contain whitespace: $cli_dir/out with space" \
"$cli_output_whitespace_log" >/dev/null; then
echo "switchkins remap generator did not report whitespace output directories clearly" >&2
sed -n '1,20p' "$cli_output_whitespace_log" >&2
exit 1
fi
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh \
--all-output-dir / "$cli_manifest" >"$cli_output_root_log" 2>&1; then
echo "switchkins remap generator accepted the filesystem root as output directory" >&2
exit 1
fi
if ! grep -F "switchkins remap output directory must not be the filesystem root" \
"$cli_output_root_log" >/dev/null; then
echo "switchkins remap generator did not report filesystem-root output directories clearly" >&2
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 --all-output-dir "$cli_dir/out" "$cli_manifest"
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh "$cli_manifest" \ LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh "$cli_manifest" \
@@ -2367,6 +2791,144 @@ JS
fi fi
} }
validate_tool_table_source_coverage_only() {
local table_file=$1
local cpp_file=$2
local json_file=$3
local api_file=$4
local wasm_core_file=$5
local types_file=$6
local label=$7
if command -v python3 >/dev/null 2>&1; then
if ! python3 - "$table_file" "$cpp_file" "$json_file" "$api_file" "$wasm_core_file" "$types_file" "$label" <<'PY'
import json
import re
import sys
table_file, cpp_file, json_file, api_file, wasm_core_file, types_file, label = sys.argv[1:]
table_row_re = re.compile(
r'^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*'
r'(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$'
)
case_row_re = re.compile(
r'^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*'
r'("(?:(?:\\.)|[^"\\])*"),\s*'
r'(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$'
)
bad_aliases = []
with open(table_file, encoding="utf-8") as handle:
for line_number, line in enumerate(handle, 1):
match = table_row_re.match(line)
if match:
alias = json.loads(match.group(1)).lower()
if alias.endswith(".tbl") or "/tool" in alias or "tool_table" in alias or "tooltable" in alias:
bad_aliases.append((line_number, alias))
bad_cases = []
with open(cpp_file, encoding="utf-8") as handle:
for line_number, line in enumerate(handle, 1):
match = case_row_re.match(line)
if match:
field = json.loads(match.group(1)).lower()
value = json.loads(match.group(2)).lower()
if "tool" in field or value.endswith(".tbl"):
bad_cases.append((line_number, field, value))
with open(json_file, encoding="utf-8") as handle:
for index, entry in enumerate(json.load(handle)):
field = entry["field"].lower()
value = entry["value"].lower()
if "tool" in field or value.endswith(".tbl"):
bad_cases.append((index, field, value))
with open(api_file, encoding="utf-8") as handle:
api_text = handle.read()
with open(wasm_core_file, encoding="utf-8") as handle:
wasm_text = handle.read()
with open(types_file, encoding="utf-8") as handle:
types_text = handle.read()
bad_bridge_tokens = []
for token, text in {
"api": api_text,
"wasm": wasm_text,
"types": types_text,
}.items():
if re.search(r'\btoolTable\b|\btooltable\b|\btool_table\b|\bTOOL_TABLE\b', text):
bad_bridge_tokens.append(token)
if bad_aliases:
raise SystemExit(f"{label} generated TOOL_TABLE alias(es), but TOOL_TABLE is source coverage only: {bad_aliases}")
if bad_cases:
raise SystemExit(f"{label} generated TOOL_TABLE case(s), but TOOL_TABLE is source coverage only: {bad_cases}")
if bad_bridge_tokens:
raise SystemExit(f"{label} exposes TOOL_TABLE through switchkins API/web bridge: {bad_bridge_tokens}")
PY
then
exit 1
fi
elif command -v node >/dev/null 2>&1; then
if ! node - "$table_file" "$cpp_file" "$json_file" "$api_file" "$wasm_core_file" "$types_file" "$label" <<'JS'
const fs = require("fs");
const [tableFile, cppFile, jsonFile, apiFile, wasmCoreFile, typesFile, label] = process.argv.slice(2);
const tableRowRe = /^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$/;
const caseRowRe = /^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*("(?:(?:\\.)|[^"\\])*"),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$/;
const badAliases = [];
fs.readFileSync(tableFile, "utf8").split(/\r?\n/).forEach((line, index) => {
const match = tableRowRe.exec(line);
if (!match) {
return;
}
const alias = JSON.parse(match[1]).toLowerCase();
if (alias.endsWith(".tbl") || alias.includes("/tool") || alias.includes("tool_table") || alias.includes("tooltable")) {
badAliases.push([index + 1, alias]);
}
});
const badCases = [];
fs.readFileSync(cppFile, "utf8").split(/\r?\n/).forEach((line, index) => {
const match = caseRowRe.exec(line);
if (!match) {
return;
}
const field = JSON.parse(match[1]).toLowerCase();
const value = JSON.parse(match[2]).toLowerCase();
if (field.includes("tool") || value.endsWith(".tbl")) {
badCases.push([index + 1, field, value]);
}
});
JSON.parse(fs.readFileSync(jsonFile, "utf8")).forEach((entry, index) => {
const field = entry.field.toLowerCase();
const value = entry.value.toLowerCase();
if (field.includes("tool") || value.endsWith(".tbl")) {
badCases.push([index, field, value]);
}
});
const bridgeTokenRe = /\btoolTable\b|\btooltable\b|\btool_table\b|\bTOOL_TABLE\b/;
const badBridgeTokens = [
["api", fs.readFileSync(apiFile, "utf8")],
["wasm", fs.readFileSync(wasmCoreFile, "utf8")],
["types", fs.readFileSync(typesFile, "utf8")],
].filter(([_name, text]) => bridgeTokenRe.test(text)).map(([name]) => name);
if (badAliases.length > 0) {
throw new Error(`${label} generated TOOL_TABLE alias(es), but TOOL_TABLE is source coverage only: ${JSON.stringify(badAliases)}`);
}
if (badCases.length > 0) {
throw new Error(`${label} generated TOOL_TABLE case(s), but TOOL_TABLE is source coverage only: ${JSON.stringify(badCases)}`);
}
if (badBridgeTokens.length > 0) {
throw new Error(`${label} exposes TOOL_TABLE through switchkins API/web bridge: ${badBridgeTokens.join(",")}`);
}
JS
then
exit 1
fi
else
echo "missing python3 or node for switchkins TOOL_TABLE source-only validation" >&2
exit 1
fi
}
for generated_file in \ for generated_file in \
linuxcnc_switchkins_remap_table.inc \ linuxcnc_switchkins_remap_table.inc \
linuxcnc_switchkins_remap_config_cases.inc \ linuxcnc_switchkins_remap_config_cases.inc \
@@ -2387,6 +2949,22 @@ validate_web_switchkins_case_fields \
web/src/wasm-core.js \ web/src/wasm-core.js \
web/src/index.ts \ web/src/index.ts \
"generated" "generated"
validate_tool_table_source_coverage_only \
"$generated_dir/linuxcnc_switchkins_remap_table.inc" \
"$generated_dir/linuxcnc_switchkins_remap_config_cases.inc" \
"$generated_dir/linuxcnc_switchkins_remap_config_cases.json" \
core/src/cnc_sim_api.cpp \
web/src/wasm-core.js \
web/src/index.ts \
"generated"
validate_tool_table_source_coverage_only \
core/src/linuxcnc_switchkins_remap_table.inc \
core/tests/linuxcnc_switchkins_remap_config_cases.inc \
web/public/linuxcnc_switchkins_remap_config_cases.json \
core/src/cnc_sim_api.cpp \
web/src/wasm-core.js \
web/src/index.ts \
"tracked"
validate_generated_header \ validate_generated_header \
"$generated_dir/linuxcnc_switchkins_remap_table.inc" \ "$generated_dir/linuxcnc_switchkins_remap_table.inc" \

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,194 @@
#!/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,
"python-compile-covered": 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")
if sections.get("python") != sections.get("python-compile-covered"):
raise SystemExit("blocked Python sources are no longer fully compile-covered by the inert Python C API probe")
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",
"Python/Boost.Python blockers are compile-covered by the inert Python C API probe",
"./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

@@ -5,6 +5,9 @@ option(CNC_SIM_ENABLE_LINUXCNC_BRIDGE "Build the experimental LinuxCNC canon bri
option(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND "Build the native LinuxCNC librs274 backend" OFF) option(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND "Build the native LinuxCNC librs274 backend" OFF)
option(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE "Compile the browser-safe subset of LinuxCNC rs274 sources" OFF) option(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE "Compile the browser-safe subset of LinuxCNC rs274 sources" OFF)
option(CNC_SIM_ENABLE_SMOKE_BACKEND "Build the legacy explicit smoke G-code backend test harness" OFF) option(CNC_SIM_ENABLE_SMOKE_BACKEND "Build the legacy explicit smoke G-code backend test harness" OFF)
if(CNC_SIM_ENABLE_SMOKE_BACKEND AND CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE)
message(FATAL_ERROR "CNC_SIM_ENABLE_SMOKE_BACKEND cannot be combined with CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE")
endif()
set(CNC_SIM_LINUXCNC_ROOT "" CACHE PATH "LinuxCNC source root for the experimental bridge") set(CNC_SIM_LINUXCNC_ROOT "" CACHE PATH "LinuxCNC source root for the experimental bridge")
set(CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST set(CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST
"${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc-rs274-wasm-source-files.txt" "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc-rs274-wasm-source-files.txt"

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) { 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; unit_scale_ = scale;
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_UNITS, line); CncSimEvent event = base_event(CNC_SIM_EVENT_SET_UNITS, line);
event.feed = scale; event.feed = scale;

View File

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

View File

@@ -11,6 +11,7 @@
struct CncSimHandle { struct CncSimHandle {
CncSimDialect dialect = CNC_SIM_DIALECT_LINUXCNC; CncSimDialect dialect = CNC_SIM_DIALECT_LINUXCNC;
GcodeBackendKind backend = GcodeBackendKind::LinuxCncRs274; GcodeBackendKind backend = GcodeBackendKind::LinuxCncRs274;
GcodeBackendOptions backend_options;
std::string last_error; std::string last_error;
CanonEventSink sink; CanonEventSink sink;
}; };
@@ -96,6 +97,72 @@ bool contains_string_value(const std::string &compact, const char *key, const ch
return false; return false;
} }
bool find_json_string_value(const std::string &json, const char *key, std::string *value) {
const std::string needle = std::string("\"") + key + "\"";
size_t pos = 0;
while ((pos = json.find(needle, pos)) != std::string::npos) {
pos += needle.size();
while (pos < json.size() && std::isspace(static_cast<unsigned char>(json[pos]))) {
++pos;
}
if (pos >= json.size() || json[pos] != ':') {
continue;
}
++pos;
while (pos < json.size() && std::isspace(static_cast<unsigned char>(json[pos]))) {
++pos;
}
if (pos >= json.size() || json[pos] != '"') {
continue;
}
++pos;
std::string parsed;
bool escaped = false;
for (; pos < json.size(); ++pos) {
const char ch = json[pos];
if (escaped) {
switch (ch) {
case '"':
case '\\':
case '/':
parsed.push_back(ch);
break;
case 'n':
parsed.push_back('\n');
break;
case 'r':
parsed.push_back('\r');
break;
case 't':
parsed.push_back('\t');
break;
default:
parsed.push_back(ch);
break;
}
escaped = false;
} else if (ch == '\\') {
escaped = true;
} else if (ch == '"') {
*value = parsed;
return true;
} else {
parsed.push_back(ch);
}
}
}
return false;
}
bool find_json_string_any(const std::string &json, std::string *value, const char *first) {
return find_json_string_value(json, first, value);
}
template <typename... Keys>
bool find_json_string_any(const std::string &json, std::string *value, const char *first, Keys... rest) {
return find_json_string_value(json, first, value) || find_json_string_any(json, value, rest...);
}
bool contains_switchkins_config_alias(const std::string &compact, const char *alias) { bool contains_switchkins_config_alias(const std::string &compact, const char *alias) {
return contains_string_value(compact, "switchkins", alias) || return contains_string_value(compact, "switchkins", alias) ||
contains_string_value(compact, "remap", alias) || contains_string_value(compact, "remap", alias) ||
@@ -300,6 +367,20 @@ int apply_config(CncSimHandle *handle, const std::string &json) {
return -1; return -1;
} }
std::string ini_file_name;
if (find_json_string_any(json,
&ini_file_name,
"ini",
"iniFile",
"inifile",
"iniFileName",
"inifilename",
"ini_file",
"ini_file_name",
"INI_FILE_NAME")) {
handle->backend_options.ini_file_name = ini_file_name;
}
bool rtcp_enabled = false; bool rtcp_enabled = false;
double tool_length = 0.0; double tool_length = 0.0;
const bool has_rtcp_enabled = contains_bool_value(compact, "rtcp", &rtcp_enabled) || const bool has_rtcp_enabled = contains_bool_value(compact, "rtcp", &rtcp_enabled) ||
@@ -383,7 +464,12 @@ int cnc_sim_parse_program(CncSimHandle *handle, const char *program, size_t prog
} }
handle->last_error.clear(); handle->last_error.clear();
const int rc = parse_gcode_with_backend(handle->backend, handle->sink, program, program_len, &handle->last_error); const int rc = parse_gcode_with_backend(handle->backend,
handle->sink,
program,
program_len,
handle->backend_options,
&handle->last_error);
if (rc != 0 && handle->last_error.empty()) { if (rc != 0 && handle->last_error.empty()) {
handle->last_error = "program parse failed"; handle->last_error = "program parse failed";
} }

View File

@@ -22,6 +22,7 @@ int parse_gcode_with_backend(GcodeBackendKind backend,
CanonEventSink &sink, CanonEventSink &sink,
const char *program, const char *program,
size_t program_len, size_t program_len,
const GcodeBackendOptions &options,
std::string *error) { std::string *error) {
switch (backend) { switch (backend) {
case GcodeBackendKind::Smoke: { case GcodeBackendKind::Smoke: {
@@ -37,7 +38,7 @@ int parse_gcode_with_backend(GcodeBackendKind backend,
} }
case GcodeBackendKind::LinuxCncRs274: case GcodeBackendKind::LinuxCncRs274:
#ifdef CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND #ifdef CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
return parse_linuxcnc_rs274_backend(sink, program, program_len, error); return parse_linuxcnc_rs274_backend(sink, program, program_len, options, error);
#else #else
if (error) { if (error) {
*error = "linuxcnc-rs274 backend is not compiled into this build"; *error = "linuxcnc-rs274 backend is not compiled into this build";

View File

@@ -10,11 +10,15 @@ enum class GcodeBackendKind {
LinuxCncRs274, LinuxCncRs274,
}; };
struct GcodeBackendOptions {
std::string ini_file_name;
};
const char *gcode_backend_name(GcodeBackendKind backend); const char *gcode_backend_name(GcodeBackendKind backend);
int parse_gcode_with_backend(GcodeBackendKind backend, int parse_gcode_with_backend(GcodeBackendKind backend,
CanonEventSink &sink, CanonEventSink &sink,
const char *program, const char *program,
size_t program_len, size_t program_len,
const GcodeBackendOptions &options,
std::string *error); std::string *error);

View File

@@ -193,7 +193,7 @@ void INIT_CANON() {
void USE_LENGTH_UNITS(CANON_UNITS units) { void USE_LENGTH_UNITS(CANON_UNITS units) {
trace_call("USE_LENGTH_UNITS"); trace_call("USE_LENGTH_UNITS");
if (active_sink) { 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"); trace_call("SET_SPINDLE_SPEED");
if (active_sink) { 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) { void SET_NAIVECAM_TOLERANCE(double tolerance) {
naivecam_tolerance = 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) { void SET_CUTTER_RADIUS_COMPENSATION(double) {
@@ -477,21 +486,21 @@ void SET_SPINDLE_MODE(int spindle, double mode) {
void SPINDLE_RETRACT_TRAVERSE() { void SPINDLE_RETRACT_TRAVERSE() {
} }
void START_SPINDLE_CLOCKWISE(int, int) { void START_SPINDLE_CLOCKWISE(int spindle, int) {
if (active_sink) { 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) { 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) { 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() { 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() { 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() { double GET_EXTERNAL_ANGLE_UNITS() {

View File

@@ -82,6 +82,37 @@ bool file_mode_enabled() {
return std::getenv("CNC_SIM_RS274_FILE_MODE") != nullptr; return std::getenv("CNC_SIM_RS274_FILE_MODE") != nullptr;
} }
class ScopedEnvironmentVariable {
public:
ScopedEnvironmentVariable(const char *name, const std::string &value) : name_(name), active_(!value.empty()) {
if (!active_) {
return;
}
if (const char *old = std::getenv(name_)) {
had_old_ = true;
old_ = old;
}
setenv(name_, value.c_str(), 1);
}
~ScopedEnvironmentVariable() {
if (!active_) {
return;
}
if (had_old_) {
setenv(name_, old_.c_str(), 1);
} else {
unsetenv(name_);
}
}
private:
const char *name_;
bool active_;
bool had_old_ = false;
std::string old_;
};
#ifndef CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE #ifndef CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE
bool write_temp_program(const char *program, bool write_temp_program(const char *program,
size_t program_len, size_t program_len,
@@ -175,6 +206,7 @@ int execute_file_mode(InterpBase *interp,
int parse_linuxcnc_rs274_backend(CanonEventSink &sink, int parse_linuxcnc_rs274_backend(CanonEventSink &sink,
const char *program, const char *program,
size_t program_len, size_t program_len,
const GcodeBackendOptions &options,
std::string *error) { std::string *error) {
if (!program && program_len != 0) { if (!program && program_len != 0) {
if (error) { if (error) {
@@ -183,14 +215,36 @@ int parse_linuxcnc_rs274_backend(CanonEventSink &sink,
return -1; return -1;
} }
// LinuxCNC source basis: rs274ngc_pre.cc uses
// RS274NGC_PARAMETER_FILE_NAME_DEFAULT when the external parameter file
// name is empty. Reset the bridge every parse so a prior native env
// override does not leak into later default parses.
if (const char *parameter_file = std::getenv("CNC_SIM_RS274_VAR")) { if (const char *parameter_file = std::getenv("CNC_SIM_RS274_VAR")) {
SET_PARAMETER_FILE_NAME(parameter_file); SET_PARAMETER_FILE_NAME(parameter_file);
} else {
SET_PARAMETER_FILE_NAME("rs274ngc.var");
} }
cnc_sim_init_minimal_linuxcnc_tooldata(); cnc_sim_init_minimal_linuxcnc_tooldata();
sink.clear_callback_status(); sink.clear_callback_status();
cnc_sim_linuxcnc_set_canon_sink(&sink); cnc_sim_linuxcnc_set_canon_sink(&sink);
// LinuxCNC source basis: rs274ngc_pre.cc and interp_namedparams.cc read
// INI_FILE_NAME for startup and #<_ini[section]key> named parameters.
ScopedEnvironmentVariable ini_file_env("INI_FILE_NAME", options.ini_file_name);
InterpBase *interp = makeInterp(); InterpBase *interp = makeInterp();
if (!options.ini_file_name.empty()) {
// LinuxCNC source basis: emctask.cc calls Interp::ini_load() before
// Interp::init(), and rs274ngc_pre.cc ini_load() maps
// [RS274NGC] PARAMETER_FILE through SET_PARAMETER_FILE_NAME().
if (interp->ini_load(options.ini_file_name.c_str()) != INTERP_OK) {
if (error) {
*error = "ini_load failed: " + options.ini_file_name;
}
delete interp;
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return -1;
}
}
int status = interp->init(); int status = interp->init();
if (status != INTERP_OK) { if (status != INTERP_OK) {
if (error) { if (error) {

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "canon_event_sink.h" #include "canon_event_sink.h"
#include "gcode_backend.h"
#include <cstddef> #include <cstddef>
#include <string> #include <string>
@@ -8,5 +9,5 @@
int parse_linuxcnc_rs274_backend(CanonEventSink &sink, int parse_linuxcnc_rs274_backend(CanonEventSink &sink,
const char *program, const char *program,
size_t program_len, size_t program_len,
const GcodeBackendOptions &options,
std::string *error); std::string *error);

View File

@@ -2,8 +2,11 @@
#include <cstdlib> #include <cstdlib>
#include <cmath> #include <cmath>
#include <filesystem>
#include <fstream>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <system_error>
#include <vector> #include <vector>
namespace { namespace {
@@ -61,6 +64,34 @@ private:
std::string old_; std::string old_;
}; };
class ScopedTempDir {
public:
explicit ScopedTempDir(const char *prefix) {
const char *tmpdir = std::getenv("TMPDIR");
std::string tmpl = std::string(tmpdir ? tmpdir : "/tmp") + "/" + prefix + ".XXXXXX";
std::vector<char> buffer(tmpl.begin(), tmpl.end());
buffer.push_back('\0');
char *dir = mkdtemp(buffer.data());
if (!dir) {
std::perror("mkdtemp");
std::exit(1);
}
path_ = dir;
}
~ScopedTempDir() {
std::error_code error;
std::filesystem::remove_all(path_, error);
}
std::string file(const char *name) const {
return path_ + "/" + name;
}
private:
std::string path_;
};
bool saw_comment_state(const std::vector<CncSimEvent> &events, bool saw_comment_state(const std::vector<CncSimEvent> &events,
int line, int line,
int reserved, int reserved,
@@ -103,6 +134,17 @@ bool saw_kinematics_switch(const std::vector<CncSimEvent> &events, int line, int
return false; return false;
} }
bool is_ini_file_name_field(const std::string &field) {
return field == "ini" ||
field == "iniFile" ||
field == "inifile" ||
field == "iniFileName" ||
field == "inifilename" ||
field == "ini_file" ||
field == "ini_file_name" ||
field == "INI_FILE_NAME";
}
bool expect_switchkins_remap_config(const std::string &json, bool expect_switchkins_remap_config(const std::string &json,
int m428_type, int m428_type,
int m429_type, int m429_type,
@@ -168,6 +210,8 @@ bool expect_switchkins_remap_config(const std::string &json,
} // namespace } // namespace
int main() { int main() {
ScopedTempDir temp_dir("cnc_sim_api_linuxcnc_rs274_smoke");
{ {
std::vector<CncSimEvent> default_events; std::vector<CncSimEvent> default_events;
CncSimHandle *default_sim = cnc_sim_create(); CncSimHandle *default_sim = cnc_sim_create();
@@ -196,6 +240,108 @@ int main() {
} }
} }
{
const std::string ini_parameter_var_path = temp_dir.file("ini_parameter.var");
{
std::ofstream var_file(ini_parameter_var_path);
var_file << "5001\t44.000000\n";
}
const std::string rs274_ini_path = temp_dir.file("rs274_parameter.ini");
{
std::ofstream ini_file(rs274_ini_path);
ini_file << "[RS274NGC]\n";
ini_file << "PARAMETER_FILE = " << ini_parameter_var_path << "\n";
ini_file << "[SETUP]\n";
ini_file << "XPOS = 12\n";
}
std::vector<CncSimEvent> ini_events;
CncSimHandle *ini_sim = cnc_sim_create();
cnc_sim_set_event_callback(ini_sim, collect_event, &ini_events);
const std::string ini_config =
std::string("{\"backend\":\"linuxcnc-rs274\",\"iniFileName\":\"") +
rs274_ini_path +
"\"}";
bool ini_ok = true;
ini_ok &= expect(cnc_sim_load_config_json(ini_sim, ini_config.c_str(), ini_config.size()) == 0,
cnc_sim_last_error(ini_sim));
ScopedEnv file_mode_env("CNC_SIM_RS274_FILE_MODE", "1");
const char ini_parameter_check_program[] =
"G21 G90 G17\n"
"F100\n"
"G1 X#5001\n"
"G1 X#<_ini[setup]xpos>\n"
"M30\n";
const int ini_parse_rc = cnc_sim_parse_program(ini_sim,
ini_parameter_check_program,
sizeof(ini_parameter_check_program) - 1);
const std::string ini_parse_error = cnc_sim_last_error(ini_sim);
ini_ok &= expect(ini_parse_rc == 0, ini_parse_error.c_str());
bool saw_ini_parameter_restore = false;
bool saw_ini_named_parameter = false;
for (const auto &event : ini_events) {
saw_ini_parameter_restore = saw_ini_parameter_restore ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 3 &&
near(event.end.x, 44.0));
saw_ini_named_parameter = saw_ini_named_parameter ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 4 &&
near(event.end.x, 12.0));
}
// LinuxCNC source basis: emctask.cc calls interp.ini_load(), and
// rs274ngc_pre.cc ini_load() maps [RS274NGC] PARAMETER_FILE through
// SET_PARAMETER_FILE_NAME before rs274_ngc_init() restores parameters.
ini_ok &= expect(saw_ini_parameter_restore,
"expected LinuxCNC INI PARAMETER_FILE to restore numbered parameters");
// LinuxCNC source basis: rs274ngc_pre.cc and interp_namedparams.cc
// read INI_FILE_NAME for #<_ini[section]key> lookups.
ini_ok &= expect(saw_ini_named_parameter,
"expected LinuxCNC config INI_FILE_NAME to back #<_ini[]> named parameters");
cnc_sim_destroy(ini_sim);
if (!ini_ok) {
return 1;
}
}
{
std::vector<CncSimEvent> percent_events;
CncSimHandle *percent_sim = cnc_sim_create();
cnc_sim_set_event_callback(percent_sim, collect_event, &percent_events);
ScopedEnv file_mode_env("CNC_SIM_RS274_FILE_MODE", "1");
const char percent_program[] =
"%\n"
"G21 G90\n"
"F100\n"
"G1 X1\n"
"%\n"
"G1 X99\n";
bool percent_ok = true;
percent_ok &= expect(cnc_sim_parse_program(percent_sim,
percent_program,
sizeof(percent_program) - 1) == 0,
cnc_sim_last_error(percent_sim));
std::vector<int> feed_lines;
std::vector<double> feed_x;
for (const auto &event : percent_events) {
if (event.type == CNC_SIM_EVENT_LINEAR_FEED) {
feed_lines.push_back(event.line);
feed_x.push_back(event.end.x);
}
}
// LinuxCNC source basis: rs274ngc_pre.cc open() enables percent-file
// mode when the first nonblank line is '%'; interp_read.cc read_text()
// stops at the matching '%', so later program text is ignored.
percent_ok &= expect(feed_lines.size() == 1 &&
feed_lines[0] == 4 &&
feed_x.size() == 1 &&
near(feed_x[0], 1.0),
"expected LinuxCNC percent-file mode to ignore trailing text");
cnc_sim_destroy(percent_sim);
if (!percent_ok) {
return 1;
}
}
const char config[] = const char config[] =
"{\"backend\":\"linuxcnc-rs274\",\"rtcp\":{\"enabled\":true,\"toolLength\":100,\"toolLengths\":{\"7\":125}}}"; "{\"backend\":\"linuxcnc-rs274\",\"rtcp\":{\"enabled\":true,\"toolLength\":100,\"toolLengths\":{\"7\":125}}}";
const char program[] = const char program[] =
@@ -1688,6 +1834,13 @@ int main() {
#include "linuxcnc_switchkins_remap_config_cases.inc" #include "linuxcnc_switchkins_remap_config_cases.inc"
}; };
for (const SourceBackedConfigCase &source_case : source_cases) { for (const SourceBackedConfigCase &source_case : source_cases) {
// LinuxCNC rs274ngc_pre.cc ini_load() loads REMAP NGC/Python
// dependencies for INI_FILE_NAME. The native API probe above
// covers real INI loading; generated switchkins INI aliases stay
// covered by the table checker and smoke bridge probes.
if (is_ini_file_name_field(source_case.field)) {
continue;
}
const std::string json = std::string("{\"backend\":\"linuxcnc-rs274\",\"") + const std::string json = std::string("{\"backend\":\"linuxcnc-rs274\",\"") +
source_case.field + source_case.field +
"\":\"" + "\":\"" +

View File

@@ -6163,6 +6163,48 @@ int main() {
"expected LinuxCNC rotary/UVW persistent numbered parameters to reload from parameter file"); "expected LinuxCNC rotary/UVW persistent numbered parameters to reload from parameter file");
} }
const std::string parameter_env_leak_guard_var_path = temp_dir.file("parameter_env_leak_guard.var");
{
std::ofstream var_file(parameter_env_leak_guard_var_path);
var_file << "5001\t17.000000\n";
}
{
ScopedEnv var_env("CNC_SIM_RS274_VAR", parameter_env_leak_guard_var_path.c_str());
const char parameter_env_leak_guard_set_program[] =
"#5001 = 91\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
parameter_env_leak_guard_set_program,
sizeof(parameter_env_leak_guard_set_program) - 1) == 0,
cnc_sim_last_error(sim));
}
{
const char parameter_env_default_check_program[] =
"F100\n"
"O10 if [#5001 EQ 91]\n"
"G1 X1\n"
"O10 endif\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim,
parameter_env_default_check_program,
sizeof(parameter_env_default_check_program) - 1) == 0,
cnc_sim_last_error(sim));
bool saw_stale_env_parameter = false;
for (const auto &event : events) {
saw_stale_env_parameter = saw_stale_env_parameter ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.line == 3 &&
near(event.end.x, 1.0));
}
// LinuxCNC source basis: rs274ngc_pre.cc rs274_ngc_init() falls back
// to RS274NGC_PARAMETER_FILE_NAME_DEFAULT when the external parameter
// file name is empty. The project env override must not persist after
// CNC_SIM_RS274_VAR is unset.
ok &= expect(!saw_stale_env_parameter,
"expected LinuxCNC parameter env override not to leak into default parses");
}
const char volatile_global_set_program[] = const char volatile_global_set_program[] =
"#31 = 7\n" "#31 = 7\n"
"M30\n"; "M30\n";

View File

@@ -160,6 +160,7 @@ int main() {
bool saw_adaptive_feed_off = false; bool saw_adaptive_feed_off = false;
bool saw_feed_hold_on = false; bool saw_feed_hold_on = false;
bool saw_feed_hold_off = false; bool saw_feed_hold_off = false;
bool saw_naivecam_tolerance = false;
bool saw_speed_feed_sync_start = false; bool saw_speed_feed_sync_start = false;
bool saw_speed_feed_sync_stop = false; bool saw_speed_feed_sync_stop = false;
bool saw_arc = false; bool saw_arc = false;
@@ -287,6 +288,10 @@ int main() {
event.line == 29 && event.line == 29 &&
event.reserved == 53 && event.reserved == 53 &&
event.feed == 0.0); 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 && saw_speed_feed_sync_start = saw_speed_feed_sync_start || (event.type == CNC_SIM_EVENT_COMMENT &&
event.reserved == 3301 && event.reserved == 3301 &&
event.feed == 1.25 && 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_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_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(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_on, "expected block-delete query to reflect enable");
ok &= expect(external_block_delete_off, "expected block-delete query to reflect disable"); 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"); ok &= expect(external_optional_stop_on, "expected optional-stop query to reflect enable");

View File

@@ -85,6 +85,18 @@ struct _inittab {
PyObject *(*initfunc)(void); 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 PyCFunction = PyObject *(*)(PyObject *, PyObject *);
using getter = PyObject *(*)(PyObject *, void *); using getter = PyObject *(*)(PyObject *, void *);
using setter = int (*)(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_ParseTuple(PyObject *, const char *, ...);
int PyArg_VaParse(PyObject *, const char *, va_list); int PyArg_VaParse(PyObject *, const char *, va_list);
PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...); PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...);
PyObject *PyObject_Call(PyObject *, PyObject *, PyObject *);
PyObject *PyObject_GetAttrString(PyObject *, const char *); PyObject *PyObject_GetAttrString(PyObject *, const char *);
int PyObject_SetAttrString(PyObject *, const char *, PyObject *); int PyObject_SetAttrString(PyObject *, const char *, PyObject *);
int PyObject_IsTrue(PyObject *); int PyObject_IsTrue(PyObject *);
@@ -165,6 +178,17 @@ Py_ssize_t PySequence_Length(PyObject *);
PyObject *Py_BuildValue(const char *, ...); PyObject *Py_BuildValue(const char *, ...);
PyObject *PyModule_Create(PyModuleDef *); PyObject *PyModule_Create(PyModuleDef *);
int PyModule_AddObject(PyObject *, const char *, PyObject *); 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 *); int PyType_Ready(PyTypeObject *);
PyObject *PyType_GenericNew(PyTypeObject *, PyObject *, PyObject *); PyObject *PyType_GenericNew(PyTypeObject *, PyObject *, PyObject *);
void *PyObject_NewShim(std::size_t, PyTypeObject *); void *PyObject_NewShim(std::size_t, PyTypeObject *);

View File

@@ -1,14 +1,30 @@
#include "cnc_sim_api.h" #include "cnc_sim_api.h"
#include "boost/python/object.hpp"
#include "hal.h"
#include "libnml/nml/stat_msg.hh"
#include <cmath> #include <cmath>
#include <cstdarg>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <string> #include <string>
#include <vector> #include <vector>
#include <wordexp.h> #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(), // LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides read(),
// execute(), and wordexp() paths; interp_convert.cc converts G0, G2, and M30. // 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 { namespace {
int collect_event(const CncSimEvent *event, void *user_data) { int collect_event(const CncSimEvent *event, void *user_data) {
@@ -57,6 +73,119 @@ bool wordexp_expands_environment_variables() {
return ok; 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 } // namespace
int main() { int main() {
@@ -66,6 +195,21 @@ int main() {
if (!wordexp_expands_environment_variables()) { if (!wordexp_expands_environment_variables()) {
return 6; 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; std::vector<CncSimEvent> events;
CncSimHandle *sim = cnc_sim_create(); CncSimHandle *sim = cnc_sim_create();

View File

@@ -1,8 +1,38 @@
#include "dlfcn.h"
#include "interp_base.hh" #include "interp_base.hh"
#include <cstring>
// LinuxCNC source basis: src/emc/rs274ngc/interp_base.cc provides // 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() { int main() {
InterpBase *interp = interp_from_shlib("nonexistent-interpreter.so"); if (dlopen(nullptr, RTLD_GLOBAL) != nullptr) {
return interp == nullptr ? 0 : 1; 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; status = RCS_STATUS::UNINITIALIZED;
_state = -1; _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 "boost/python/object.hpp"
#include <cstdlib> #include <cstdlib>
#include <cstring>
// LinuxCNC source basis: src/emc/rs274ngc/interp_python.cc, // LinuxCNC source basis: src/emc/rs274ngc/interp_python.cc,
// interp_namedparams.cc, gcodemodule.cc, and src/emc/pythonplugin/python_plugin.cc // interp_namedparams.cc, gcodemodule.cc, and src/emc/pythonplugin/python_plugin.cc
@@ -8,7 +9,11 @@
extern "C" { extern "C" {
static PyTypeObject none_type = {"NoneType"}; static PyTypeObject none_type = {"NoneType"};
static PyTypeObject module_type = {"module"};
static PyTypeObject dict_type = {"dict"};
static PyObject none_object = {&none_type}; 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_KeyError = reinterpret_cast<PyObject *>(1);
PyObject *PyExc_KeyboardInterrupt = reinterpret_cast<PyObject *>(2); PyObject *PyExc_KeyboardInterrupt = reinterpret_cast<PyObject *>(2);
@@ -111,6 +116,10 @@ PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...) {
return &none_object; return &none_object;
} }
PyObject *PyObject_Call(PyObject *, PyObject *, PyObject *) {
return &none_object;
}
PyObject *PyObject_GetAttrString(PyObject *, const char *) { PyObject *PyObject_GetAttrString(PyObject *, const char *) {
return &none_object; return &none_object;
} }
@@ -187,6 +196,64 @@ int PyModule_AddObject(PyObject *, const char *, PyObject *) {
return 0; 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 *) { int PyType_Ready(PyTypeObject *) {
return 0; return 0;
} }

View File

@@ -1,7 +1,13 @@
#include "pythonplugin/python_plugin.hh" #include "pythonplugin/python_plugin.hh"
#include <algorithm>
#include <cstdio> #include <cstdio>
#include <cstdlib> #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 // LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc provides the
// native PythonPlugin implementation called by RS274 Python/remap paths. // native PythonPlugin implementation called by RS274 Python/remap paths.
@@ -11,10 +17,139 @@ __attribute__((weak)) std::string handle_pyerror() {
PythonPlugin *python_plugin = nullptr; 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; static PythonPlugin plugin;
if (!python_plugin) {
python_plugin = &plugin; python_plugin = &plugin;
python_plugin->status = PLUGIN_OK; 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; return python_plugin;
} }
@@ -42,9 +177,36 @@ int PythonPlugin::configure(const char *iniFilename, const char *section) {
} }
std::fclose(file); std::fclose(file);
status = PLUGIN_OK; 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; 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 *) { bool PythonPlugin::is_callable(const char *, const char *) {
return false; return false;
@@ -58,6 +220,9 @@ int PythonPlugin::call(const char *,
if (!callable) { if (!callable) {
return PLUGIN_NO_CALLABLE; return PLUGIN_NO_CALLABLE;
} }
if (status < PLUGIN_OK) {
return status;
}
retval = boost::python::object(); retval = boost::python::object();
exception_msg = handle_pyerror(); 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) { 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(); retval = boost::python::object();
status = PLUGIN_OK; status = PLUGIN_OK;
return status; return status;
} }
int PythonPlugin::initialize() { 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; 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 "pythonplugin/python_plugin.hh"
#include <string>
#include <cstdlib>
#include <cstdio>
// LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and // LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and
// python_plugin.hh define the PythonPlugin API exercised by this link probe. // python_plugin.hh define the PythonPlugin API exercised by this link probe.
int main() { int main() {
@@ -13,13 +17,271 @@ int main() {
if (plugin->configure(nullptr, nullptr) != PLUGIN_NO_SECTION) { if (plugin->configure(nullptr, nullptr) != PLUGIN_NO_SECTION) {
return 6; 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) { if (plugin->configure("/tmp/cnc_sim_missing_python_plugin_probe.ini", "PYTHON") != PLUGIN_BAD_INIFILE) {
return 7; return 7;
} }
if (plugin->usable()) {
return 27;
}
boost::python::object retval; 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) { if (plugin->run_string("noop", retval, false) != PLUGIN_OK) {
return 3; 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 *section_path_append_ini = "/tmp/cnc_sim_python_plugin_probe_section_path_append.ini";
{
FILE *file = std::fopen(section_path_append_ini, "w");
if (!file) {
std::remove(ini);
return 50;
}
std::fputs("[REMAP]\n"
"[PYTHON]\nTOPLEVEL = /tmp/cnc_sim_missing_python_section_toplevel.py\n"
"PATH_APPEND = ~/linuxcnc-python\n",
file);
std::fclose(file);
}
unsetenv("HOME");
// LinuxCNC source basis: python_plugin.cc configure() looks up TOPLEVEL in
// the caller-provided section, but PATH_APPEND entries are read from the
// fixed PYTHON section before initialize(); a PYTHON TOPLEVEL is ignored
// when the caller configures a different section.
if (plugin->configure(section_path_append_ini, "REMAP") != PLUGIN_EXCEPTION_DURING_PATH_APPEND) {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(section_path_append_ini);
return 51;
}
if (plugin->last_errmsg() != "bad path append") {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(section_path_append_ini);
return 52;
}
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(section_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) { if (plugin->call(nullptr, nullptr, boost::python::object(), boost::python::object(), retval) != PLUGIN_NO_CALLABLE) {
return 8; return 8;
} }
@@ -35,6 +297,9 @@ int main() {
if (plugin->last_exception().empty()) { if (plugin->last_exception().empty()) {
return 12; return 12;
} }
if (plugin->last_exception() != handle_pyerror()) {
return 25;
}
if (plugin->run_string("noop", retval, false) != PLUGIN_OK) { if (plugin->run_string("noop", retval, false) != PLUGIN_OK) {
return 13; return 13;
} }

View File

@@ -2,7 +2,8 @@
#include <cstdio> #include <cstdio>
// LinuxCNC source basis: src/rtapi/uspace_common.h provides rtapi_snprintf() // 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, ...) { extern "C" int rtapi_snprintf(char *buf, unsigned long size, const char *fmt, ...) {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
@@ -10,3 +11,7 @@ extern "C" int rtapi_snprintf(char *buf, unsigned long size, const char *fmt, ..
va_end(ap); va_end(ap);
return rc; 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,83 @@ int main() {
return 9; 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;
}
CANON_TOOL_TABLE spindle_only = tooldata_entry_init();
spindle_only.toolno = 99;
spindle_only.pocketno = 0;
spindle_only.offset.tran.z = 9.0;
if (tooldata_put(spindle_only, 0) != IDX_OK) {
return 21;
}
CANON_TOOL_TABLE saved_nonspindle = tooldata_entry_init();
saved_nonspindle.toolno = 7;
saved_nonspindle.pocketno = 5;
saved_nonspindle.offset.tran.z = 4.25;
if (tooldata_put(saved_nonspindle, 1) == IDX_FAIL) {
return 26;
}
std::string nonrandom_save_template = make_temp_template("cnc_tooldata_common_nonrandom_save_XXXXXX");
std::vector<char> nonrandom_save_path(nonrandom_save_template.begin(), nonrandom_save_template.end());
nonrandom_save_path.push_back('\0');
const int nonrandom_save_fd = mkstemp(nonrandom_save_path.data());
if (nonrandom_save_fd < 0) {
return 22;
}
close(nonrandom_save_fd);
// LinuxCNC source basis: src/emc/tooldata/tooldata_common.cc saves
// nonrandom tool tables starting at pocket 1, so spindle pocket 0 is not
// emitted unless DB_ACTIVE selects the one-entry spindle save path.
if (tooldata_save(nonrandom_save_path.data()) != 0) {
std::remove(nonrandom_save_path.data());
return 23;
}
FILE *nonrandom_save_fp = std::fopen(nonrandom_save_path.data(), "r");
if (!nonrandom_save_fp) {
std::remove(nonrandom_save_path.data());
return 24;
}
char nonrandom_saved[CANON_TOOL_ENTRY_LEN * 2] = {};
std::fread(nonrandom_saved, 1, sizeof(nonrandom_saved) - 1, nonrandom_save_fp);
std::fclose(nonrandom_save_fp);
std::remove(nonrandom_save_path.data());
if (std::strstr(nonrandom_saved, "T99") || !std::strstr(nonrandom_saved, "T7")) {
return 25;
}
tooldata_init(true);
tooldata_set_db(DB_ACTIVE); tooldata_set_db(DB_ACTIVE);
if (tooldata_load(path.data()) != -1) { if (tooldata_load(path.data()) != -1) {
return 10; return 10;

View File

@@ -84,6 +84,71 @@ int main() {
return 45; 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) { if (tool_mmap_user() != -1) {
return 25; return 25;
} }
@@ -113,6 +178,18 @@ int main() {
if (tooldata_db_getall() != -1) { if (tooldata_db_getall() != -1) {
return 28; 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 = std::string missing_db_program_with_arg_string =
make_temp_path("cnc_tooldata_probe_missing_db_program arg"); make_temp_path("cnc_tooldata_probe_missing_db_program arg");
std::vector<char> missing_db_program_with_arg( std::vector<char> missing_db_program_with_arg(
@@ -126,6 +203,19 @@ int main() {
if (separator == std::string::npos || missing_db_program_with_arg[separator] != 0) { if (separator == std::string::npos || missing_db_program_with_arg[separator] != 0) {
return 56; return 56;
} }
char missing_db_program_with_args[] = "/tmp/cnc_tooldata_probe_missing_db_program alpha beta";
if (tooldata_db_init(missing_db_program_with_args, 0) != -1) {
return 83;
}
// LinuxCNC src/emc/tooldata/tooldata_db.cc tokenizes progname_plus_args
// in place with strtok_r() before checking whether argv[0] is executable.
if (missing_db_program_with_args[42] != 0 ||
missing_db_program_with_args[48] != 0 ||
std::strcmp(missing_db_program_with_args, "/tmp/cnc_tooldata_probe_missing_db_program") != 0 ||
std::strcmp(missing_db_program_with_args + 43, "alpha") != 0 ||
std::strcmp(missing_db_program_with_args + 49, "beta") != 0) {
return 84;
}
std::string missing_db_program_string = make_temp_path("cnc_tooldata_probe_missing_db_program"); std::string missing_db_program_string = make_temp_path("cnc_tooldata_probe_missing_db_program");
std::vector<char> missing_db_program( std::vector<char> missing_db_program(
missing_db_program_string.begin(), missing_db_program_string.begin(),
@@ -147,6 +237,12 @@ int main() {
} }
alignas(EMC_TOOL_STAT) unsigned char toolstat_storage[sizeof(EMC_TOOL_STAT)] = {}; alignas(EMC_TOOL_STAT) unsigned char toolstat_storage[sizeof(EMC_TOOL_STAT)] = {};
auto *toolstat = reinterpret_cast<EMC_TOOL_STAT *>(toolstat_storage); 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) { if (tool_mmap_creator(toolstat, 1) != 0) {
return 15; return 15;
} }
@@ -189,6 +285,23 @@ int main() {
if (tooldata_put(tool, 12) != IDX_NEW) { if (tooldata_put(tool, 12) != IDX_NEW) {
return 2; return 2;
} }
CANON_TOOL_TABLE lower_index_tool = tooldata_entry_init();
lower_index_tool.toolno = 3;
lower_index_tool.pocketno = 5;
// LinuxCNC src/emc/tooldata/tooldata_mmap.cc returns IDX_OK for writes at
// or below the current last_index and leaves last_index unchanged.
if (tooldata_put(lower_index_tool, 5) != IDX_OK) {
return 80;
}
if (tooldata_last_index_get() != 12) {
return 81;
}
CANON_TOOL_TABLE lower_index_loaded = tooldata_entry_init();
if (tooldata_get(&lower_index_loaded, 5) != IDX_OK ||
lower_index_loaded.toolno != 3 ||
lower_index_loaded.pocketno != 5) {
return 82;
}
CANON_TOOL_TABLE loaded = tooldata_entry_init(); CANON_TOOL_TABLE loaded = tooldata_entry_init();
if (tooldata_get(&loaded, 12) != IDX_OK) { if (tooldata_get(&loaded, 12) != IDX_OK) {
@@ -300,6 +413,16 @@ int main() {
reloaded.offset.tran.z != 3.5 || reloaded.diameter != 8.0) { reloaded.offset.tran.z != 3.5 || reloaded.diameter != 8.0) {
return 14; 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); tooldata_init(false);
std::string nonrandom_path_template = make_temp_path("cnc_tooldata_nonrandom_probe_XXXXXX"); std::string nonrandom_path_template = make_temp_path("cnc_tooldata_nonrandom_probe_XXXXXX");
@@ -338,5 +461,21 @@ int main() {
return 39; 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; return 0;
} }

View File

@@ -1,5 +1,6 @@
#include "tooldata.hh" #include "tooldata.hh"
#include <cstdio>
#include <cstring> #include <cstring>
#include <unistd.h> #include <unistd.h>
@@ -15,7 +16,11 @@ bool db_live = false;
extern "C" { extern "C" {
int tool_nml_register(CANON_TOOL_TABLE *tblptr) { int tool_nml_register(CANON_TOOL_TABLE *tblptr) {
return tblptr ? 0 : -1; if (!tblptr) {
std::fprintf(stderr, "%5d!!!PROBLEM tool_nml_register()\n", getpid());
return -1;
}
return 0;
} }
int tooldata_db_init(char progname_plus_args[], int) { int tooldata_db_init(char progname_plus_args[], int) {
@@ -29,16 +34,22 @@ int tooldata_db_init(char progname_plus_args[], int) {
for (char *token = program; token; token = strtok_r(nullptr, " ", &saveptr)) { for (char *token = program; token; token = strtok_r(nullptr, " ", &saveptr)) {
++argc; ++argc;
if (argc >= MAX_DB_PROGRAM_ARGS) { if (argc >= MAX_DB_PROGRAM_ARGS) {
std::fprintf(stderr,
"!!!!db_init: argc exceeds MAX_DB_PROGRAM_ARGS=%d\n",
MAX_DB_PROGRAM_ARGS);
return -1; return -1;
} }
} }
if (!program || access(program, X_OK) != 0) { if (!program || access(program, X_OK) != 0) {
std::fprintf(stderr, "!!!!db_init: <%s> not executable\n", program ? program : "");
return -1; return -1;
} }
return -1; return -1;
} }
int tooldata_db_notify(tool_notify_t, int, int, CANON_TOOL_TABLE) { 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; 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 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 pass should advance one workstream context only; crossing into another context
requires naming the dependency and keeping the edit set minimal. 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 ## 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

@@ -34,7 +34,7 @@ This parser only exists to test the ABI and UI before Emscripten and LinuxCNC ar
- spindle modes `G96`, `G97` - spindle modes `G96`, `G97`
- IJK and R arcs - IJK and R arcs
- numbered and named parameter assignment/reference such as `#1 = ...`, `#<name> = ...`, `X#1`, and nested expressions like `X[[#<name> + 2] * 3]` - numbered and named parameter assignment/reference such as `#1 = ...`, `#<name> = ...`, `X#1`, and nested expressions like `X[[#<name> + 2] * 3]`
- expression functions `ABS[]`, `SQRT[]`, `EXP[]`, `LN[]`, degree-based `SIN[]`/`COS[]`/`TAN[]`/`ASIN[]`/`ACOS[]`, LinuxCNC-style `ATAN[]/[]`, and `FIX[]`/`FUP[]`/`ROUND[]` - expression functions `ABS[]`, `SQRT[]`, `EXP[]`, `LN[]`, degree-based `SIN[]`/`COS[]`/`TAN[]`/`ASIN[]`/`ACOS[]`, LinuxCNC-style `ATAN[]/[]`, `FIX[]`/`FUP[]`/`ROUND[]`, and source-linked `mod`/`**`/logical/relational operator precedence
- numeric and named O-word subprograms such as `O100 call` and `O<name> call` - numeric and named O-word subprograms such as `O100 call` and `O<name> call`
- canned cycles `G73`, `G74`, `G81`, `G82`, `G83`, `G84`, `G85`, `G86`, `G87`, `G88`, `G89` with `G80`, `G98`, `G99`, `L` - canned cycles `G73`, `G74`, `G81`, `G82`, `G83`, `G84`, `G85`, `G86`, `G87`, `G88`, `G89` with `G80`, `G98`, `G99`, `L`
- coordinate offset events for `G10 L2`, `G10 L20`, `G54`-`G59.3`, `G52`, `G92`, `G92.1`, `G92.2`, and `G92.3` - coordinate offset events for `G10 L2`, `G10 L20`, `G54`-`G59.3`, `G52`, `G92`, `G92.1`, `G92.2`, and `G92.3`
@@ -190,27 +190,40 @@ This matrix tracks LinuxCNC feature coverage for the web/WASM simulator. A featu
| Area | Status | Regression | | Area | Status | Regression |
| --- | --- | --- | | --- | --- | --- |
| Basic modal motion `G0/G1/G2/G3` | covered, including `G18`/`G19` arc plane mapping and `G90.1`/`G91.1` IJK modes | `tests/gcode/linuxcnc_basic_motion.ngc`, `tests/gcode/basic_mill.ngc`, `tests/gcode/linuxcnc_arc_planes.ngc`, `tests/gcode/linuxcnc_arc_distance_modes.ngc` | | Basic modal motion `G0/G1/G2/G3` | covered, including `G91` incremental linear motion, R-format arcs, `G18`/`G19` arc plane mapping, `G90.1`/`G91.1` IJK modes, and native/source center-format arc tolerance acceptance/rejection | `tests/gcode/linuxcnc_basic_motion.ngc`, `tests/gcode/basic_mill.ngc`, `tests/gcode/incremental_and_r_arc.ngc`, `tests/gcode/linuxcnc_arc_planes.ngc`, `tests/gcode/linuxcnc_arc_distance_modes.ngc`, `tests/gcode/linuxcnc_arc_center_tolerance_good_imperial.ngc`, `tests/gcode/linuxcnc_arc_center_tolerance_bad_metric.ngc` |
| Polar coordinates and extended-plane motion restrictions | covered through LinuxCNC native/source `@`/`^` polar parsing, G17-only polar motion validation, absolute/incremental polar endpoint mapping, G53/origin rejection, and extended-plane arc/NURBS rejection | `tests/gcode/linuxcnc_polar_coordinates.ngc`, `tests/gcode/linuxcnc_polar_coordinate_variants.ngc`, `tests/gcode/linuxcnc_polar_g18_error.ngc`, `tests/gcode/linuxcnc_polar_with_x_error.ngc`, `tests/gcode/linuxcnc_polar_with_y_error.ngc`, `tests/gcode/linuxcnc_polar_g92_error.ngc`, `tests/gcode/linuxcnc_polar_g53_error.ngc`, `tests/gcode/linuxcnc_polar_radius_origin_error.ngc`, `tests/gcode/linuxcnc_polar_incremental_radius_origin_error.ngc`, `tests/gcode/linuxcnc_polar_incremental_theta_origin_error.ngc`, `tests/gcode/linuxcnc_extended_plane_arc_error.ngc`, `tests/gcode/linuxcnc_g62_extended_plane_error.ngc` |
| Block-level axis/motion validation | covered through LinuxCNC native/source `enhance_block()` checks for invalid `G80` axes, missing `G52/G92` or motion axes, bare axes without active motion, and modal-0 axis consumers on motion blocks | `tests/gcode/linuxcnc_g80_axis_error.ngc`, `tests/gcode/linuxcnc_g52_missing_axis_error.ngc`, `tests/gcode/linuxcnc_g92_missing_axis_error.ngc`, `tests/gcode/linuxcnc_motion_missing_axis_error.ngc`, `tests/gcode/linuxcnc_axis_without_motion_error.ngc`, `tests/gcode/linuxcnc_motion_with_g92_axis_error.ngc` |
| Predefined position moves `G28/G28.1`, `G30/G30.1` | covered for stored position, waypoint, selected-axis return, and all-axis return | `tests/gcode/linuxcnc_predefined_positions.ngc` | | Predefined position moves `G28/G28.1`, `G30/G30.1` | covered for stored position, waypoint, selected-axis return, and all-axis return | `tests/gcode/linuxcnc_predefined_positions.ngc` |
| Machine-coordinate move `G53` | covered for explicit/modal `G0`, `G1`, and incremental-mode rejection in smoke parser | `tests/gcode/linuxcnc_machine_coordinates.ngc` | | Machine-coordinate move `G53` | covered for explicit/modal `G0`, `G1`, arc rejection, and incremental-mode rejection through LinuxCNC native/source backends | `tests/gcode/linuxcnc_machine_coordinates.ngc`, `tests/gcode/linuxcnc_g53_arc_error.ngc`, `tests/gcode/linuxcnc_g53_incremental_error.ngc` |
| Tool select/change `T... M6` | covered with minimal native tooldata | `tests/gcode/basic_mill.ngc` | | Tool select/change `T... M6` | covered with minimal native tooldata, including native/source `find_tool_index()` rejection of missing `T...` and `M61 Q...` tools | `tests/gcode/basic_mill.ngc`, `tests/gcode/linuxcnc_t_missing_tool_error.ngc`, `tests/gcode/linuxcnc_m61_missing_tool_error.ngc` |
| Tool length offset `G43/G43.1/G43.2/G49` | covered with nonzero tool-table offset, dynamic replacement, additive axis offsets, `G43.2 H...`, and clear through LinuxCNC native/source backends | `tests/gcode/linuxcnc_tool_length.ngc`, `tests/gcode/linuxcnc_dynamic_tool_length.ngc` | | Tool length offset `G43/G43.1/G43.2/G49` | covered with nonzero tool-table offset, dynamic replacement, additive axis offsets, `G43.2 H...`, clear, native/source missing-tool rejection, and native/source `G43.2` H/axis validation | `tests/gcode/linuxcnc_tool_length.ngc`, `tests/gcode/linuxcnc_dynamic_tool_length.ngc`, `tests/gcode/linuxcnc_g43_missing_tool_error.ngc`, `tests/gcode/linuxcnc_g43_2_missing_tool_error.ngc`, `tests/gcode/linuxcnc_g43_2_h_axis_error.ngc`, `tests/gcode/linuxcnc_g43_2_missing_h_axis_error.ngc` |
| RTCP controls `G43.4/G43.5/G49` | covered as simulator-owned control lines | `tests/gcode/linuxcnc_rtcp_controls.ngc` | | RTCP controls `G43.4/G43.5/G49` | covered as simulator-owned control lines | `tests/gcode/linuxcnc_rtcp_controls.ngc` |
| Kinematics switch `M428/M429/M430` | covered as simulator-owned control lines | `tests/gcode/linuxcnc_rtcp_controls.ngc` | | Kinematics switch `M428/M429/M430` | covered as simulator-owned control lines | `tests/gcode/linuxcnc_rtcp_controls.ngc` |
| Spindle, coolant, program stops, and current tool number `M3/M4/M5`, `M7/M8/M9`, `M0/M1/M60/M30`, `M61 Q...` | covered through LinuxCNC native/source backends | `tests/gcode/linuxcnc_spindle_direction.ngc`, `tests/gcode/linuxcnc_coolant.ngc`, `tests/gcode/linuxcnc_program_stops.ngc`, `tests/gcode/linuxcnc_tool_number.ngc` | | Spindle, coolant, program stops, and current tool number `M3/M4/M5`, `M7/M8/M9`, `M0/M1/M2/M60/M30/M99`, `M61 Q...` | covered through LinuxCNC native/source backends, including native/source `convert_m()` rejection of out-of-range `M3/M4/M5 $` spindle selectors and negative `M61 Q`, plus `convert_stop()` coverage for M2 reset/program-end and default main-program M99 program-end without M30-only pallet shuttle | `tests/gcode/linuxcnc_spindle_direction.ngc`, `tests/gcode/linuxcnc_coolant.ngc`, `tests/gcode/linuxcnc_program_stops.ngc`, `tests/gcode/linuxcnc_program_end_m2.ngc`, `tests/gcode/linuxcnc_program_end_m99.ngc`, `tests/gcode/linuxcnc_tool_number.ngc`, `tests/gcode/linuxcnc_m3_spindle_dollar_out_of_range_error.ngc`, `tests/gcode/linuxcnc_m4_spindle_dollar_out_of_range_error.ngc`, `tests/gcode/linuxcnc_m5_spindle_dollar_out_of_range_error.ngc`, `tests/gcode/linuxcnc_m61_negative_q_error.ngc` |
| Spindle speed mode `G96/G97` | covered with Canon `SET_SPINDLE_MODE` preserved as mode state events, including `G96 D...` max-RPM value | `tests/gcode/linuxcnc_spindle_modes.ngc` | | Spindle speed mode `G96/G97` | covered with Canon `SET_SPINDLE_MODE` preserved as mode state events, including `G96 D...` max-RPM value and native/source `check_other_codes()` rejection of missing `S` with `G96` | `tests/gcode/linuxcnc_spindle_modes.ngc`, `tests/gcode/linuxcnc_g96_missing_s_word_error.ngc` |
| Spindle orientation `M19 R... P... Q...` | covered with Canon `ORIENT_SPINDLE` and `WAIT_SPINDLE_ORIENT_COMPLETE` preserved as temporary mode state events | `tests/gcode/linuxcnc_spindle_orient.ngc` | | Spindle orientation `M19 R... P... Q...` | covered with Canon `ORIENT_SPINDLE` and `WAIT_SPINDLE_ORIENT_COMPLETE` preserved as temporary mode state events, including native/source `check_other_codes()` validation for `P/R` words and `convert_m()` rejection of out-of-range `$` selectors and nonpositive `Q` waits | `tests/gcode/linuxcnc_spindle_orient.ngc`, `tests/gcode/linuxcnc_m19_spindle_dollar_out_of_range_error.ngc`, `tests/gcode/linuxcnc_m19_p_not_integer_error.ngc`, `tests/gcode/linuxcnc_m19_p_out_of_range_error.ngc`, `tests/gcode/linuxcnc_m19_r_out_of_range_error.ngc`, `tests/gcode/linuxcnc_m19_zero_q_error.ngc` |
| Spindle-synchronized feed and threading `G33/G33.1/G76` | covered through smoke, LinuxCNC native, and source-linked paths with Canon `START_SPEED_FEED_SYNCH`/`STOP_SPEED_FEED_SYNCH` preserved, rigid-tap line numbers normalized, and G76 multi-pass threading smoke-covered | `tests/gcode/linuxcnc_threading_sync.ngc`, `tests/gcode/linuxcnc_threading_cycle.ngc`, smoke API regression | | Spindle-synchronized feed and threading `G33/G33.1/G76` | covered through smoke, LinuxCNC native, and source-linked paths with Canon `START_SPEED_FEED_SYNCH`/`STOP_SPEED_FEED_SYNCH` preserved, rigid-tap line numbers normalized, G76 multi-pass threading smoke-covered, native/source `check_other_codes()` validation for required/disallowed `K/F/P/I/J` words, and native/source `convert_straight()`/`convert_threading_cycle()` coverage for standalone G76 and rejection of invalid spindle state, spindle selector, rotary-axis motion, and G76 `I/J/K` geometry | `tests/gcode/linuxcnc_threading_sync.ngc`, `tests/gcode/linuxcnc_threading_cycle.ngc`, `tests/gcode/linuxcnc_g76only.ngc`, `tests/gcode/linuxcnc_g33_missing_k_word_error.ngc`, `tests/gcode/linuxcnc_g33_f_word_error.ngc`, `tests/gcode/linuxcnc_g33_1_missing_k_word_error.ngc`, `tests/gcode/linuxcnc_g33_1_f_word_error.ngc`, `tests/gcode/linuxcnc_g76_missing_p_word_error.ngc`, `tests/gcode/linuxcnc_g76_missing_i_word_error.ngc`, `tests/gcode/linuxcnc_g76_missing_j_word_error.ngc`, `tests/gcode/linuxcnc_g76_missing_k_word_error.ngc`, `tests/gcode/linuxcnc_g33_stopped_spindle_error.ngc`, `tests/gcode/linuxcnc_g33_1_stopped_spindle_error.ngc`, `tests/gcode/linuxcnc_g33_spindle_dollar_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g33_1_spindle_dollar_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g76_spindle_dollar_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g76_stopped_spindle_error.ngc`, `tests/gcode/linuxcnc_g76_rotary_axis_error.ngc`, `tests/gcode/linuxcnc_g76_i_zero_error.ngc`, `tests/gcode/linuxcnc_g76_j_zero_error.ngc`, `tests/gcode/linuxcnc_g76_k_less_equal_j_error.ngc`, smoke API regression |
| Lathe diameter/radius mode `G7/G8` | covered in smoke parser for X-axis diameter/radius scaling, including interaction with G76 threading geometry; native/source backends use LinuxCNC interpreter state directly | smoke API regression | | Lathe diameter/radius mode `G7/G8` | covered through LinuxCNC native/source backends for same-block X scaling, `G76` I/J/K threading geometry interaction, and `#<_lathe_diameter_mode>`/`#<_lathe_radius_mode>` readonly named-parameter state | `tests/gcode/linuxcnc_lathe_diameter_mode.ngc`, `tests/gcode/linuxcnc_lathe_diameter_g76.ngc` |
| Digital/analog I/O `M62`-`M68` | covered with Canon digital output, analog output, and input wait callbacks preserved as temporary state events | `tests/gcode/linuxcnc_io_controls.ngc` | | Digital/analog I/O `M62`-`M68` | covered with Canon digital output, analog output, and input wait callbacks preserved as temporary state events, including native/source `convert_m()` validation for required `P`/`E`, M66 input selection, M66 wait timeout, and analog wait restrictions | `tests/gcode/linuxcnc_io_controls.ngc`, `tests/gcode/linuxcnc_m62_missing_p_word_error.ngc`, `tests/gcode/linuxcnc_m63_missing_p_word_error.ngc`, `tests/gcode/linuxcnc_m64_missing_p_word_error.ngc`, `tests/gcode/linuxcnc_m65_missing_p_word_error.ngc`, `tests/gcode/linuxcnc_m66_missing_input_error.ngc`, `tests/gcode/linuxcnc_m66_both_inputs_error.ngc`, `tests/gcode/linuxcnc_m66_zero_timeout_error.ngc`, `tests/gcode/linuxcnc_m66_analog_wait_error.ngc`, `tests/gcode/linuxcnc_m67_missing_e_word_error.ngc`, `tests/gcode/linuxcnc_m68_missing_e_word_error.ngc` |
| Override controls `M48`-`M53` | covered with Canon feed override, spindle speed override, adaptive feed, and feed hold callbacks preserved as temporary state events | `tests/gcode/linuxcnc_override_controls.ngc` | | Override controls `M48`-`M53` | covered with Canon feed override, spindle speed override, adaptive feed, and feed hold callbacks preserved as temporary state events | `tests/gcode/linuxcnc_override_controls.ngc` |
| 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` | | 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 | partially covered in smoke parser for common modal state reads used by O-word macros: `#<_absolute>`, `#<_incremental>`, `#<_metric>`, `#<_imperial>`, `#<_feed>`, `#<_rpm>`, `#<_spindle_on>`, `#<_spindle_cw>`, `#<_lathe_diameter_mode>`, and `#<_lathe_radius_mode>` | smoke API regression | | 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` |
| 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` | | 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` |
| Canned cycle `G81/G80` | covered for drilling expand-to-canon path, including `G18/G19` plane-specific depth-axis mapping for representative drilling/boring cycles | `tests/gcode/linuxcnc_canned_cycle.ngc`, `tests/gcode/linuxcnc_canned_cycle_planes.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` | | 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` |
| Cutter compensation | covered for tool-table `G41/G42 D...` and explicit-radius `G41.1/G42.1/G40` through LinuxCNC native/source backends | `tests/gcode/linuxcnc_cutter_comp.ngc` | | NURBS `G5.2/G5.3/G6.2` | covered through LinuxCNC native/source backends for `G18` XZ-plane endpoint mapping from `convert_nurbs()` | `tests/gcode/linuxcnc_g52_xz_nurbs.ngc`, `tests/gcode/linuxcnc_g62_xz_nurbs.ngc` |
| Cutter compensation | covered for tool-table `G41/G42 D...` and explicit-radius `G41.1/G42.1/G40` through LinuxCNC native/source backends, plus native/source LinuxCNC rejection of tool changes, M66 waits, G71.0, retract-mode changes, arc exits, and gouging while compensation is active | `tests/gcode/linuxcnc_cutter_comp.ngc`, `tests/gcode/linuxcnc_cutter_comp_tool_change_error.ngc`, `tests/gcode/linuxcnc_cutter_comp_wait_input_error.ngc`, `tests/gcode/linuxcnc_cutter_comp_g71_error.ngc`, `tests/gcode/linuxcnc_canned_cycle_g98_retract_error.ngc`, `tests/gcode/linuxcnc_ccomp_arcexit_error.ngc`, `tests/gcode/linuxcnc_ccomp_gouging_error.ngc` |
| Probe moves `G38.2/G38.3/G38.4/G38.5` | covered as distinct probe events with LinuxCNC `probe_type` preserved through smoke, native, and source-linked paths | `tests/gcode/linuxcnc_probe_no_error.ngc` | | Probe moves `G38.2/G38.3/G38.4/G38.5` | covered as distinct probe events with LinuxCNC `probe_type` preserved through smoke, native, and source-linked paths | `tests/gcode/linuxcnc_probe_no_error.ngc` |
| O-word subroutines, calls, and control flow | covered for numeric and named `O... sub/call/return/endsub` plus `if`/`elseif`/`else`, `while`, `repeat`, `do`/`while`, `break`, and `continue` through LinuxCNC file mode; smoke parser also covers named `O<name>` sub/call/return/endsub and loop-control forms | `tests/gcode/smoke_oword_subprogram.ngc`, `tests/gcode/linuxcnc_named_oword_subprogram.ngc`, `tests/gcode/linuxcnc_oword_control_flow.ngc` | | O-word and Fanuc subroutines, calls, and control flow | covered for numeric and named `O... sub/call/return/endsub`, Fanuc-style `M98 P... L...`/`M99`, plus `if`/`elseif`/`else`, `while`, `repeat`, `do`/`while`, `break`, and `continue` through LinuxCNC file mode; native/source read errors cover malformed O-word commands, missing control-expression brackets, too many or empty call parameters, Fanuc-style `M98` missing `P`, and midline/comment-prefixed `M98` falling through to normal M-code handling; native/source execution errors cover duplicate/undefined/unmatched labels, out-of-context `return`/`endsub`, wrong subroutine close style, nested subroutine definitions, numbered subroutine illegal-location rejection, main-file EOF after numeric O-word return, too many nested call levels, lowercase numeric labels, O-word state parameters, `EXISTS[]` word-value checks, and M98/O-word local-parameter style differences; smoke parser also covers named `O<name>` sub/call/return/endsub and loop-control forms | `tests/gcode/smoke_oword_subprogram.ngc`, `tests/gcode/linuxcnc_named_oword_subprogram.ngc`, `tests/gcode/smoke_subprogram_m98.ngc`, `tests/gcode/linuxcnc_oword_control_flow.ngc`, `tests/gcode/linuxcnc_value_returned.ngc`, `tests/gcode/linuxcnc_call_level.ngc`, `tests/gcode/linuxcnc_exists_word_value.ngc`, `tests/gcode/linuxcnc_lowercase_numeric_oword.ngc`, `tests/gcode/linuxcnc_m98_mixed_styles_variables.ngc`, `tests/gcode/linuxcnc_numeric_oword_main_eof_error.ngc`, `tests/gcode/linuxcnc_numeric_oword_sub_illegal_location.ngc`, `tests/gcode/linuxcnc_oword_unknown_command_error.ngc`, `tests/gcode/linuxcnc_oword_unexpected_trailing_item_error.ngc`, `tests/gcode/linuxcnc_oword_if_missing_bracket_error.ngc`, `tests/gcode/linuxcnc_oword_elseif_missing_bracket_error.ngc`, `tests/gcode/linuxcnc_oword_while_missing_bracket_error.ngc`, `tests/gcode/linuxcnc_oword_repeat_missing_bracket_error.ngc`, `tests/gcode/linuxcnc_oword_too_many_call_parameters_error.ngc`, `tests/gcode/linuxcnc_oword_empty_call_parameter_error.ngc`, `tests/gcode/linuxcnc_oword_too_many_call_levels_error.ngc`, `tests/gcode/linuxcnc_oword_endsub_without_sub_error.ngc`, `tests/gcode/linuxcnc_oword_return_without_sub_error.ngc`, `tests/gcode/linuxcnc_oword_duplicate_if_label_error.ngc`, `tests/gcode/linuxcnc_oword_elseif_undefined_label_error.ngc`, `tests/gcode/linuxcnc_oword_else_undefined_label_error.ngc`, `tests/gcode/linuxcnc_oword_endif_undefined_label_error.ngc`, `tests/gcode/linuxcnc_oword_break_undefined_label_error.ngc`, `tests/gcode/linuxcnc_oword_continue_undefined_label_error.ngc`, `tests/gcode/linuxcnc_oword_endwhile_undefined_label_error.ngc`, `tests/gcode/linuxcnc_oword_elseif_no_matching_if_error.ngc`, `tests/gcode/linuxcnc_oword_break_no_matching_loop_error.ngc`, `tests/gcode/linuxcnc_oword_continue_no_matching_loop_error.ngc`, `tests/gcode/linuxcnc_oword_endrepeat_no_matching_repeat_error.ngc`, `tests/gcode/linuxcnc_oword_endif_no_matching_if_error.ngc`, `tests/gcode/linuxcnc_oword_endwhile_no_matching_while_error.ngc`, `tests/gcode/linuxcnc_oword_fanuc_requires_m99_error.ngc`, `tests/gcode/linuxcnc_oword_sub_requires_endsub_error.ngc`, `tests/gcode/linuxcnc_nested_subroutine_definition_error.ngc`, `tests/gcode/linuxcnc_m98_missing_p_word_error.ngc`, `tests/gcode/linuxcnc_midline_m98_error.ngc`, `tests/gcode/linuxcnc_comment_prefixed_m98_error.ngc` |
| Broader canned cycles `G73`, `G74`, `G82`-`G89` | partially covered: `G73`, `G74`, `G82`, `G83`, `G84`, `G85`, `G86`, `G87`, `G88`, `G89`; smoke parser maps supported cycles across `G17/G18/G19`, including LinuxCNC's special non-XY `G74/G84` tapping argument order and `G87` plane-specific `I/J/K` mapping; native/source regressions now check non-XY `G74/G84/G86/G87/G88` sequences | `tests/gcode/linuxcnc_canned_cycles_extended.ngc`, `tests/gcode/linuxcnc_canned_cycle_planes.ngc`, smoke API regression | | Parameter expressions and operators | covered for parameter references, native/source bracketed word-value whitespace, `mod`, `**`, logical, and relational operator precedence, plus LinuxCNC parameter-setting syntax, parameter index validation, named-parameter termination, real-number parsing, divide-by-zero, negative sqrt/power, infinity rejection, malformed and bare `ATAN[]/[]`, unary operation syntax including bare unary items, `read_operation_unary()` unknown-word branches, `read_operation()` binary-operation prefix, unknown operation, and unclosed-expression error paths from `interp_read.cc`/`interp_execute.cc` | `tests/gcode/linuxcnc_parameter_expression_assignment.ngc`, `tests/gcode/linuxcnc_word_bracket_whitespace.ngc`, `tests/gcode/linuxcnc_parameter_missing_equal_error.ngc`, `tests/gcode/linuxcnc_midline_parameter_missing_equal_error.ngc`, `tests/gcode/linuxcnc_parameter_missing_value_error.ngc`, `tests/gcode/linuxcnc_parameter_number_out_of_range_error.ngc`, `tests/gcode/linuxcnc_parameter_reference_out_of_range_error.ngc`, `tests/gcode/linuxcnc_non_integer_parameter_index_error.ngc`, `tests/gcode/linuxcnc_named_parameter_not_terminated_error.ngc`, `tests/gcode/linuxcnc_no_real_value_error.ngc`, `tests/gcode/linuxcnc_bad_number_format_error.ngc`, `tests/gcode/linuxcnc_expression_operators.ngc`, `tests/gcode/linuxcnc_negative_sqrt_error.ngc`, `tests/gcode/linuxcnc_divide_by_zero_error.ngc`, `tests/gcode/linuxcnc_negative_power_error.ngc`, `tests/gcode/linuxcnc_infinite_expression_error.ngc`, `tests/gcode/linuxcnc_atan_missing_slash_error.ngc`, `tests/gcode/linuxcnc_atan_missing_bracket_error.ngc`, `tests/gcode/linuxcnc_top_level_bare_atan_error.ngc`, `tests/gcode/linuxcnc_midline_bare_atan_error.ngc`, `tests/gcode/linuxcnc_unary_missing_bracket_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_word_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_c_word_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_e_word_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_f_word_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_l_word_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_r_word_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_s_word_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_t_word_error.ngc`, `tests/gcode/linuxcnc_unary_unknown_default_word_error.ngc`, `tests/gcode/linuxcnc_top_level_bare_unary_error.ngc`, `tests/gcode/linuxcnc_midline_bare_unary_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_a_operation_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_e_operation_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_g_operation_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_l_operation_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_m_operation_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_n_operation_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_o_operation_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_x_operation_error.ngc`, `tests/gcode/linuxcnc_binary_unknown_operation_error.ngc`, `tests/gcode/linuxcnc_unclosed_expression_error.ngc` |
| Word reader validation | LinuxCNC native/source reader errors covered for negative `F/G/H/L/M/S/T` values and repeated `A/B/C/D/E/F/H/I/J/K/L/P/Q/R/S/T/U/V/W/X/Y/Z` words from `interp_read.cc` | `tests/gcode/linuxcnc_negative_f_word_error.ngc`, `tests/gcode/linuxcnc_negative_g_code_error.ngc`, `tests/gcode/linuxcnc_negative_h_word_error.ngc`, `tests/gcode/linuxcnc_negative_l_word_error.ngc`, `tests/gcode/linuxcnc_negative_m_code_error.ngc`, `tests/gcode/linuxcnc_negative_s_word_error.ngc`, `tests/gcode/linuxcnc_negative_t_word_error.ngc`, `tests/gcode/linuxcnc_multiple_a_words_error.ngc`, `tests/gcode/linuxcnc_multiple_b_words_error.ngc`, `tests/gcode/linuxcnc_multiple_c_words_error.ngc`, `tests/gcode/linuxcnc_multiple_d_words_error.ngc`, `tests/gcode/linuxcnc_multiple_e_words_error.ngc`, `tests/gcode/linuxcnc_multiple_f_words_error.ngc`, `tests/gcode/linuxcnc_multiple_h_words_error.ngc`, `tests/gcode/linuxcnc_multiple_i_words_error.ngc`, `tests/gcode/linuxcnc_multiple_j_words_error.ngc`, `tests/gcode/linuxcnc_multiple_k_words_error.ngc`, `tests/gcode/linuxcnc_multiple_l_words_error.ngc`, `tests/gcode/linuxcnc_multiple_p_words_error.ngc`, `tests/gcode/linuxcnc_multiple_q_words_error.ngc`, `tests/gcode/linuxcnc_multiple_r_words_error.ngc`, `tests/gcode/linuxcnc_multiple_s_words_error.ngc`, `tests/gcode/linuxcnc_multiple_t_words_error.ngc`, `tests/gcode/linuxcnc_multiple_u_words_error.ngc`, `tests/gcode/linuxcnc_multiple_v_words_error.ngc`, `tests/gcode/linuxcnc_multiple_w_words_error.ngc`, `tests/gcode/linuxcnc_multiple_x_words_error.ngc`, `tests/gcode/linuxcnc_multiple_y_words_error.ngc`, `tests/gcode/linuxcnc_multiple_z_words_error.ngc` |
| G/M reader validation | LinuxCNC native/source `read_g()`/`read_m()` errors covered for range checks, unknown code tables, and same-modal-group conflicts from `interp_read.cc` and `interp_array.cc` | `tests/gcode/linuxcnc_g_code_out_of_range_error.ngc`, `tests/gcode/linuxcnc_unknown_g_code_error.ngc`, `tests/gcode/linuxcnc_two_g_codes_same_modal_group_error.ngc`, `tests/gcode/linuxcnc_m_code_greater_199_error.ngc`, `tests/gcode/linuxcnc_unknown_m_code_error.ngc`, `tests/gcode/linuxcnc_two_m_codes_same_modal_group_error.ngc` |
| Block/item reader validation | LinuxCNC native/source errors covered for repeated `$`, block-delete execution skipping, unused `D/$/E/H/I/J/K/L/P/Q/R` words, bad item-start characters, middle-of-line or comment-prefixed `N` words, comment-prefixed or midline `O` words, top-level or midline bracket items, naked top-level numbers, non-leading ATAN argument slashes, malformed leading `N` numbers, EOF/program-end file checks, unclosed/nested parenthesis comments, commented or mid-program `%` characters, overlong file lines, too many M-codes, fractional/extended N-word sequence numbers, `close_and_downcase()` case/whitespace normalization, and percent-file termination from `interp_read.cc`, `interp_internal.cc`, `rs274ngc_pre.cc`, `interp_array.cc`, and `interp_check.cc` | `tests/gcode/linuxcnc_block_delete.ngc`, `tests/gcode/linuxcnc_multiple_spindle_choice_words_error.ngc`, `tests/gcode/linuxcnc_d_word_no_use_error.ngc`, `tests/gcode/linuxcnc_spindle_dollar_no_use_error.ngc`, `tests/gcode/linuxcnc_e_word_no_use_error.ngc`, `tests/gcode/linuxcnc_h_word_no_use_error.ngc`, `tests/gcode/linuxcnc_i_word_no_use_error.ngc`, `tests/gcode/linuxcnc_j_word_no_use_error.ngc`, `tests/gcode/linuxcnc_k_word_no_use_error.ngc`, `tests/gcode/linuxcnc_l_word_no_use_error.ngc`, `tests/gcode/linuxcnc_p_word_no_use_error.ngc`, `tests/gcode/linuxcnc_q_word_no_use_error.ngc`, `tests/gcode/linuxcnc_r_word_no_use_error.ngc`, `tests/gcode/linuxcnc_bad_character_bang_error.ngc`, `tests/gcode/linuxcnc_commented_percent_error.ngc`, `tests/gcode/linuxcnc_mid_program_percent_error.ngc`, `tests/gcode/linuxcnc_bad_character_middle_n_word_error.ngc`, `tests/gcode/linuxcnc_comment_prefixed_n_word_error.ngc`, `tests/gcode/linuxcnc_comment_prefixed_spaced_n_word_error.ngc`, `tests/gcode/linuxcnc_comment_prefixed_oword_error.ngc`, `tests/gcode/linuxcnc_midline_numeric_oword_error.ngc`, `tests/gcode/linuxcnc_midline_named_oword_error.ngc`, `tests/gcode/linuxcnc_bad_character_top_left_bracket_error.ngc`, `tests/gcode/linuxcnc_bad_character_top_right_bracket_error.ngc`, `tests/gcode/linuxcnc_bad_character_midline_left_bracket_error.ngc`, `tests/gcode/linuxcnc_bad_character_midline_right_bracket_error.ngc`, `tests/gcode/linuxcnc_bad_character_top_number_error.ngc`, `tests/gcode/linuxcnc_top_level_bare_atan_error.ngc`, `tests/gcode/linuxcnc_midline_bare_atan_error.ngc`, `tests/gcode/linuxcnc_bad_n_number_format_error.ngc`, `tests/gcode/linuxcnc_file_ended_no_program_end_error.ngc`, `tests/gcode/linuxcnc_file_ended_no_percent_error.ngc`, `tests/gcode/linuxcnc_unclosed_comment_error.ngc`, `tests/gcode/linuxcnc_nested_comment_error.ngc`, generated `linuxcnc_command_too_long_error.ngc`, `tests/gcode/linuxcnc_too_many_m_codes_error.ngc`, `tests/gcode/linuxcnc_fractional_linenumbers.ngc`, `tests/gcode/linuxcnc_extended_linenumbers.ngc`, `tests/gcode/linuxcnc_close_and_downcase_words.ngc`, `tests/gcode/linuxcnc_percent_file_ignores_trailing.ngc` |
| Broader canned cycles `G73`, `G74`, `G82`-`G89` | covered for representative LinuxCNC native/source `G73`, `G74`, `G82`, `G83`, `G84`, `G85`, `G86`, `G87`, `G88`, and `G89` sequences from `interp_cycles.cc`, including peck drilling, feed-out boring, stop/restart boring, dwell/feed-out boring, special non-XY `G74/G84` tapping argument order, and `G87` plane-specific `I/J/K` mapping; smoke parser still maps supported cycles across `G17/G18/G19` | `tests/gcode/linuxcnc_canned_cycles_extended.ngc`, `tests/gcode/linuxcnc_canned_cycle_planes.ngc`, smoke API regression |
| Full source-level wasm build | covered for the current wasm-safe RS274 source set, with remaining work focused on broadening browser-hostile dependency replacement and feature coverage | `./build-wasm.sh`, `test-linuxcnc-wasm-runtime-link.sh`, `test-web-wasm-node-smoke.sh`, `test-web-wasm-browser-smoke.sh` | | Full source-level wasm build | covered for the current wasm-safe RS274 source set, with remaining work focused on broadening browser-hostile dependency replacement and feature coverage | `./build-wasm.sh`, `test-linuxcnc-wasm-runtime-link.sh`, `test-web-wasm-node-smoke.sh`, `test-web-wasm-browser-smoke.sh` |
## Fixture Coverage Audit
The following LinuxCNC fixtures are also covered by both the native `librs274` runner and the source-link runner. They remain listed here so fixture coverage cannot drift out of the porting matrix: `tests/gcode/linuxcnc_abort_hot_comment.ngc`, `tests/gcode/linuxcnc_comments.ngc`, `tests/gcode/linuxcnc_g10_invalid_l_error.ngc`, `tests/gcode/linuxcnc_g10_l10_p_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g10_l11_p_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g10_l1_missing_tool_error.ngc`, `tests/gcode/linuxcnc_g10_l1_p_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g10_l1_q_negative_error.ngc`, `tests/gcode/linuxcnc_g10_l1_q_noninteger_error.ngc`, `tests/gcode/linuxcnc_g10_l1_q_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g10_l1_without_offsets_error.ngc`, `tests/gcode/linuxcnc_g10_l20_i_word_error.ngc`, `tests/gcode/linuxcnc_g10_l20_p_missing_error.ngc`, `tests/gcode/linuxcnc_g10_l20_p_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g10_l20_r_word_error.ngc`, `tests/gcode/linuxcnc_g10_l2_i_word_error.ngc`, `tests/gcode/linuxcnc_g10_l2_p_missing_error.ngc`, `tests/gcode/linuxcnc_g10_l2_p_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g10_p_non_integer_error.ngc`, `tests/gcode/linuxcnc_g4_arc_p_conflict_error.ngc`, `tests/gcode/linuxcnc_g52_missing_subsequent_weight_error.ngc`, `tests/gcode/linuxcnc_g52_missing_y_control_point_error.ngc`, `tests/gcode/linuxcnc_g52_noninteger_l_error.ngc`, `tests/gcode/linuxcnc_g52_nonpositive_p_error.ngc`, `tests/gcode/linuxcnc_g52_p_without_xy_after_start_error.ngc`, `tests/gcode/linuxcnc_g52_zero_feed_error.ngc`, `tests/gcode/linuxcnc_g53_after_intervening_motion_error.ngc`, `tests/gcode/linuxcnc_g53_short_control_points_error.ngc`, `tests/gcode/linuxcnc_g53_without_g52_error.ngc`, `tests/gcode/linuxcnc_g62_k_non_integer_error.ngc`, `tests/gcode/linuxcnc_g62_missing_p_order_error.ngc`, `tests/gcode/linuxcnc_g62_missing_q_mode_error.ngc`, `tests/gcode/linuxcnc_g62_p_non_integer_error.ngc`, `tests/gcode/linuxcnc_g62_q_non_integer_error.ngc`, `tests/gcode/linuxcnc_g62_q_out_of_range_error.ngc`, `tests/gcode/linuxcnc_g62_zero_feed_error.ngc`, `tests/gcode/linuxcnc_indexed_parameters.ngc`, `tests/gcode/linuxcnc_ini_named_parameter.ngc`, `tests/gcode/linuxcnc_modal_invalidate.ngc`, `tests/gcode/linuxcnc_named_parameter_exists.ngc`, `tests/gcode/linuxcnc_named_parameter_normalization.ngc`, `tests/gcode/linuxcnc_polar_g28_error.ngc`, `tests/gcode/linuxcnc_polar_g30_error.ngc`, `tests/gcode/linuxcnc_readonly_named_parameter_assign_error.ngc`, `tests/gcode/linuxcnc_same_modal_control_mode_error.ngc`, `tests/gcode/linuxcnc_same_modal_g10_g92_error.ngc`, `tests/gcode/linuxcnc_same_modal_g4_g53_error.ngc`, `tests/gcode/linuxcnc_same_modal_g52_g92_error.ngc`, `tests/gcode/linuxcnc_same_modal_g921_g922_error.ngc`, `tests/gcode/linuxcnc_same_modal_predefined_position_error.ngc`, `tests/gcode/linuxcnc_same_modal_probe_motion_error.ngc`, and `tests/gcode/linuxcnc_same_modal_threading_motion_error.ngc`.

View File

@@ -1,6 +1,8 @@
# LinuxCNC rs274 source map # 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 ## Compile assumptions
@@ -43,7 +45,6 @@ These are the first group to keep compiling while we peel away native-only depen
- `interp_remap.cc` - `interp_remap.cc`
- `interp_setup.cc` - `interp_setup.cc`
- `interp_write.cc` - `interp_write.cc`
- `inifile.cc`
- `modal_state.cc` - `modal_state.cc`
- `nurbs_additional_functions.cc` - `nurbs_additional_functions.cc`
- `rs274ngc_pre.cc` - `rs274ngc_pre.cc`
@@ -61,6 +62,114 @@ These are not needed for the browser simulator ABI and should not be part of the
- `pyinterp1.cc` - `pyinterp1.cc`
- `pyparamclass.cc` - `pyparamclass.cc`
### WASM-safe source core
These are the LinuxCNC source files compiled by the browser-safe source-link
probes from `linuxcnc-rs274-wasm-source-files.txt`:
- `src/emc/rs274ngc/interp_arc.cc`
- `src/emc/rs274ngc/interp_array.cc`
- `src/emc/rs274ngc/interp_base.cc`
- `src/emc/rs274ngc/interp_check.cc`
- `src/emc/rs274ngc/interp_convert.cc`
- `src/emc/rs274ngc/interp_cycles.cc`
- `src/emc/rs274ngc/interp_execute.cc`
- `src/emc/rs274ngc/interp_find.cc`
- `src/emc/rs274ngc/interp_g7x.cc`
- `src/emc/rs274ngc/interp_inspection.cc`
- `src/emc/rs274ngc/interp_internal.cc`
- `src/emc/rs274ngc/interp_inverse.cc`
- `src/emc/rs274ngc/interp_queue.cc`
- `src/emc/rs274ngc/interp_read.cc`
- `src/emc/rs274ngc/interp_setup.cc`
- `src/emc/rs274ngc/interp_write.cc`
- `src/emc/rs274ngc/modal_state.cc`
- `src/emc/rs274ngc/nurbs_additional_functions.cc`
- `src/emc/rs274ngc/interp_namedparams.cc`
- `src/emc/rs274ngc/interp_o_word.cc`
- `src/emc/rs274ngc/interp_python.cc`
- `src/emc/rs274ngc/interp_remap.cc`
- `src/emc/rs274ngc/rs274ngc_pre.cc`
- `src/emc/tooldata/tooldata_common.cc`
- `src/emc/ini/inifile.cc`
- `src/emc/nml_intf/emcops.cc`
### WASM-blocked sources
These LinuxCNC sources remain tracked in the wasm manifest, but are not part of
the browser-safe core until their Python/Boost.Python or native storage
dependencies are replaced:
- `src/emc/tooldata/tooldata_mmap.cc`
- `src/emc/rs274ngc/canonmodule.cc`
- `src/emc/rs274ngc/gcodemodule.cc`
- `src/emc/rs274ngc/interpmodule.cc`
- `src/emc/rs274ngc/pyarrays.cc`
- `src/emc/rs274ngc/pyblock.cc`
- `src/emc/rs274ngc/pyemctypes.cc`
- `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 ### Browser-hostile dependencies to replace
- Python/Boost.Python remap and named parameter hooks. - Python/Boost.Python remap and named parameter hooks.
@@ -69,6 +178,33 @@ These are not needed for the browser simulator ABI and should not be part of the
- persistent parameter file writes. - persistent parameter file writes.
- dynamic INI/HAL queries. - 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 The wasm-safe runtime now constructs its detached `EMC_STAT` through LinuxCNC
`src/emc/nml_intf/emcops.cc`. The singleton follows LinuxCNC `src/emc/nml_intf/emcops.cc`. The singleton follows LinuxCNC
`src/emc/sai/dummyemcstat.cc`, and the required `NMLmsg`/`RCS_STAT_MSG` `src/emc/sai/dummyemcstat.cc`, and the required `NMLmsg`/`RCS_STAT_MSG`

View File

@@ -18,8 +18,8 @@ Hard rules:
add browser filesystem paths that bypass OPFS. add browser filesystem paths that bypass OPFS.
- Browser app and wasm bridge code must not persist CNC programs, LinuxCNC - Browser app and wasm bridge code must not persist CNC programs, LinuxCNC
parameter files, or mirrored workspace files through non-OPFS browser storage parameter files, or mirrored workspace files through non-OPFS browser storage
APIs such as Local Storage, IndexedDB, File Picker writes, WebKit filesystem APIs such as Local Storage, IndexedDB, File Picker reads/writes, Directory
APIs, or FileReader-backed import paths. Picker access, WebKit filesystem APIs, or FileReader-backed import paths.
Allowed project code: Allowed project code:
@@ -63,8 +63,14 @@ Efficiency rules:
- Each pass should advance one reasonable, reviewable context. Do not use a - Each pass should advance one reasonable, reviewable context. Do not use a
nearby failing smoke assertion, broad fixture, or convenient touched file as nearby failing smoke assertion, broad fixture, or convenient touched file as
permission to expand the pass into another workstream. 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 - Do not add a smoke-only behavior path when the LinuxCNC-backed route already
exists; add coverage to the source-backed route instead. exists; add coverage to the source-backed route instead.
- The legacy explicit smoke backend must not be enabled together with the
LinuxCNC wasm-safe source probe; browser/WASM builds must keep the LinuxCNC
source-linked path separate from the temporary smoke parser.
- Keep browser filesystem guardrails negative as well as positive: require the - Keep browser filesystem guardrails negative as well as positive: require the
OPFS bridge and reject browser app/wasm storage APIs that could bypass it. OPFS bridge and reject browser app/wasm storage APIs that could bypass it.
- Smoke tests are verification guardrails. They must not be expanded with new - Smoke tests are verification guardrails. They must not be expanded with new
@@ -109,10 +115,28 @@ Efficiency rules:
`CNC_SIM_WASM_BLOCKERS_CACHE_DIR`. `CNC_SIM_WASM_BLOCKERS_CACHE_DIR`.
- Reuse switchkins/remap table check results in the same persistent cache with - Reuse switchkins/remap table check results in the same persistent cache with
`check-linuxcnc-switchkins-remap-table-cached.sh`; its signature must include `check-linuxcnc-switchkins-remap-table-cached.sh`; its signature must include
the cached wrapper script, checker script, generator script, the kinematics the cached wrapper script, checker script, generator script, manifest input
manifest, referenced LinuxCNC source size and nanosecond mtime stats, tracked checker scripts, the kinematics manifest, referenced LinuxCNC source size and
generated table/case outputs, and the thin API/web bridge files that pass nanosecond mtime stats, tracked generated table/case outputs, and the thin API/web bridge files that pass
switchkins config aliases. switchkins config aliases. Native, source-link, and build-wasm entry points
must run this cached check before consuming those aliases.
The uncached switchkins/remap checker must first run kinematics-manifest
completeness validation so standalone table checks also cover the
source-manifest contract used by M428/M429/M430 and RTCP coverage.
- The switchkins/remap generator must treat LinuxCNC `[EMCIO] TOOL_TABLE`
entries as source coverage only. Resolvable tool tables referenced by
M428/M429/M430 INI files must be present as `tooldata` manifest entries and
must not become new generated API aliases or browser cases.
- LinuxCNC `halcmd loadusr` parsing must stay source-tied to
`src/hal/utils/halcmd_commands.cc` `do_loadusr_cmd()` and its
`getopt("+wWin:")` option model. Generator/checker code that extracts
user-space component programs from INI/HAL `loadusr` lines must also preserve
the common `-W/-Wn/-w/-iw` forms listed by
`src/hal/utils/halcmd_completion.c`, and must skip wait component names such
as `-Wn NAME` or `-n NAME` before recording the actual program source.
- The kinematics manifest lister must preserve every allowed manifest group
used by switchkins and RTCP coverage, including `asset`, `tooldata`, and
`generated`, and must reject unknown filters or output modes for those groups.
- Reuse wasm probe objects across the wasm source object probe, - Reuse wasm probe objects across the wasm source object probe,
`rs274ngc_pre` blocker/link probes, and the runtime link probe through `rs274ngc_pre` blocker/link probes, and the runtime link probe through
`build-linuxcnc-wasm-probe-objects.sh`. Its mode-specific object caches must `build-linuxcnc-wasm-probe-objects.sh`. Its mode-specific object caches must
@@ -276,6 +300,9 @@ Build and source-link guardrails:
`rs274ngc_pre` link checks. `rs274ngc_pre` link checks.
- `build-wasm.sh` must check `cmake`, `emcmake`, `emcc`, and `node` before the - `build-wasm.sh` must check `cmake`, `emcmake`, `emcc`, and `node` before the
Emscripten configure step. Emscripten configure step.
- `build-wasm.sh` must explicitly configure `CNC_SIM_ENABLE_SMOKE_BACKEND=OFF`
while enabling `CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON`, so stale CMake
cache values cannot route browser artifacts through the legacy smoke backend.
- `build-wasm.sh` must reuse a persistent `build/wasm` configure signature - `build-wasm.sh` must reuse a persistent `build/wasm` configure signature
keyed by manifest, `core/CMakeLists.txt`, an explicit keyed by manifest, `core/CMakeLists.txt`, an explicit
`CONFIGURE_RULE_VERSION`, LinuxCNC root, and compiler launcher. The signature `CONFIGURE_RULE_VERSION`, LinuxCNC root, and compiler launcher. The signature
@@ -293,7 +320,8 @@ Build and source-link guardrails:
- The default wasm manifest partition must remain explicitly checked for 26 - The default wasm manifest partition must remain explicitly checked for 26
wasm-safe core sources and 9 blocked sources. wasm-safe core sources and 9 blocked sources.
- Manifest source listers must reject malformed lines, absolute source paths, - Manifest source listers must reject malformed lines, absolute source paths,
unknown groups, unknown filters, and duplicate source entries. unknown groups, unknown filters, unknown output modes, and duplicate source
entries.
- Native link flags must include the built LinuxCNC `lib` path and rpath. - Native link flags must include the built LinuxCNC `lib` path and rpath.
- Common CXX flag helpers must surface helper failures instead of hiding them - Common CXX flag helpers must surface helper failures instead of hiding them
behind process substitution or `read`. behind process substitution or `read`.

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,105 @@
# 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.
- `test-linuxcnc-wasm-python-c-api-symbols.sh` compiles the blocked
Python/Boost.Python binding sources with inert Python/runtime shims so their
source surface is covered without enabling Python execution.
- `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. |
| `python-compile-covered` | 8 | Blocked Python/Boost.Python module sources compiled by the inert Python C API symbol probe while remaining blockers. |
| `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.
- Python/Boost.Python blockers are compile-covered by the inert Python C API probe,
but they remain tracked blockers until Python/remap execution is ported,
wrapped, or replaced by a source-backed browser-safe LinuxCNC adaptation.
## 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.

38
generate-linuxcnc-puma560-dh-parameters.sh Normal file → Executable file
View File

@@ -3,8 +3,42 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: Puma560 DH parameter output is generated from
# configs/sim/axis/vismach/puma/puma560_dh.hal setp genserkins.{A,ALPHA,D}-N
# values used by LinuxCNC genser/switchkins simulation configs.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
usage() {
echo "usage: $0 [linuxcnc-root-relative-puma560-dh-hal]" >&2
}
case "${1:-}" in
--help|-h)
usage
exit 0
;;
--*)
usage
echo "unknown LinuxCNC puma560 DH generator option: $1" >&2
exit 1
;;
esac
if [[ -n "${2:-}" ]]; then
usage
echo "too many LinuxCNC puma560 DH generator arguments" >&2
exit 1
fi
source_path=${1:-configs/sim/axis/vismach/puma/puma560_dh.hal} source_path=${1:-configs/sim/axis/vismach/puma/puma560_dh.hal}
if [[ "$source_path" = /* || "$source_path" == .. || "$source_path" == ../* || "$source_path" == */.. || "$source_path" == */../* ]]; then
echo "LinuxCNC puma560 DH HAL source path must stay LinuxCNC-root relative: $source_path" >&2
exit 1
fi
if [[ "$source_path" != *.hal ]]; then
echo "LinuxCNC puma560 DH source must be a HAL file: $source_path" >&2
exit 1
fi
source_file="$linuxcnc_root/$source_path" source_file="$linuxcnc_root/$source_path"
if [[ ! -f "$source_file" ]]; then if [[ ! -f "$source_file" ]]; then
@@ -22,8 +56,8 @@ $1 == "setp" && $2 ~ /^genserkins\.(A|ALPHA|D)-[0-5]$/ {
split($2, qualified, ".") split($2, qualified, ".")
split(qualified[2], name_index, "-") split(qualified[2], name_index, "-")
name = name_index[1] name = name_index[1]
index = name_index[2] + 0 joint_index = name_index[2] + 0
values[name, index] = $3 values[name, joint_index] = $3
} }
END { END {

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: generated switchkins/remap tables are extracted from
# LinuxCNC configs/sim INI/HAL files and remap_subs/{428,429,430}remap.ngc
# sources listed in linuxcnc-kinematics-source-files.txt.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
mode=table mode=table
all_output_dir= all_output_dir=
@@ -56,13 +59,21 @@ if [[ ! -f "$manifest" ]]; then
echo "missing kinematics manifest: $manifest" >&2 echo "missing kinematics manifest: $manifest" >&2
exit 1 exit 1
fi fi
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest"
if [[ "$mode" == all && -z "$all_output_dir" ]]; then if [[ "$mode" == all && -z "$all_output_dir" ]]; then
echo "missing output directory for --all-output-dir" >&2 echo "missing output directory for --all-output-dir" >&2
exit 1 exit 1
fi fi
if [[ "$mode" == all && "$all_output_dir" =~ [[:space:]] ]]; then
echo "switchkins remap output directory must not contain whitespace: $all_output_dir" >&2
exit 1
fi
linuxcnc_root=$(cd "$linuxcnc_root" && pwd) linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
# LinuxCNC source basis: src/hal/utils/halcmd_commands.cc do_loadusr_cmd()
# parses loadusr options with getopt("+wWin:"), and
# src/hal/utils/halcmd_completion.c lists the common -W/-Wn/-w/-iw forms.
read_ini_switchkins_config() { read_ini_switchkins_config() {
local file=$1 local file=$1
awk ' awk '
@@ -71,6 +82,31 @@ read_ini_switchkins_config() {
sub(/[[:space:]]*$/, "", value) sub(/[[:space:]]*$/, "", value)
return value return value
} }
function loadusr_program(value, fields, field_count, i, token) {
sub(/^.*(^|[[:space:]])loadusr[[:space:]]+/, "", value)
field_count = split(value, fields, /[[:space:]]+/)
for (i = 1; i <= field_count; i++) {
token = fields[i]
if (token == "-W" || token == "-w" || token == "-i") {
continue
}
if (token == "-Wn" || token == "-n") {
i++
continue
}
if (token ~ /^-Wn.+/ || token ~ /^-n.+/) {
continue
}
if (token ~ /^-[wWi]+$/) {
continue
}
if (token ~ /^-/) {
continue
}
return token
}
return ""
}
{ {
line = $0 line = $0
sub(/[[:space:]]*[#;].*$/, "", line) sub(/[[:space:]]*[#;].*$/, "", line)
@@ -97,8 +133,26 @@ read_ini_switchkins_config() {
print "halfile\t" value print "halfile\t" value
} else if (section == "hal" && key == "postgui_halfile") { } else if (section == "hal" && key == "postgui_halfile") {
print "postgui_halfile\t" value print "postgui_halfile\t" value
} else if (section == "hal" && key == "halcmd") {
lower = tolower(value)
if (lower ~ /(^|[[:space:]])loadusr([[:space:]]|$)/) {
command = loadusr_program(value)
if (command != "") {
print "halcmd_loadusr\t" command
}
}
} else if (section == "emcio" && key == "tool_table") {
print "tool_table\t" value
} else if (section == "rs274ngc" && key == "subroutine_path") { } else if (section == "rs274ngc" && key == "subroutine_path") {
print "subroutine_path\t" value print "subroutine_path\t" value
} else if (section == "rs274ngc" && key == "on_abort_command") {
lower = tolower(value)
if (match(lower, /o[[:space:]]*<[^>]+>[[:space:]]*call/)) {
ngc = substr(value, RSTART, RLENGTH)
sub(/^[Oo][[:space:]]*</, "", ngc)
sub(/>[[:space:]]*[Cc][Aa][Ll][Ll].*$/, "", ngc)
print "on_abort_ngc\t" ngc
}
} else if (section == "rs274ngc" && key == "remap") { } else if (section == "rs274ngc" && key == "remap") {
lower = tolower(value) lower = tolower(value)
for (mcode = 428; mcode <= 430; mcode++) { for (mcode = 428; mcode <= 430; mcode++) {
@@ -109,6 +163,69 @@ read_ini_switchkins_config() {
print "remap_" mcode "\t" ngc print "remap_" mcode "\t" ngc
} }
} }
if (match(lower, /ngc[[:space:]]*=[[:space:]]*[^[:space:]]+/)) {
ngc = substr(value, RSTART, RLENGTH)
sub(/^[Nn][Gg][Cc][[:space:]]*=[[:space:]]*/, "", ngc)
print "remap_ngc\t" ngc
}
if (match(lower, /python[[:space:]]*=[[:space:]]*[^[:space:]]+/)) {
python = substr(value, RSTART, RLENGTH)
sub(/^[Pp][Yy][Tt][Hh][Oo][Nn][[:space:]]*=[[:space:]]*/, "", python)
print "python_remap\t" python
}
} else if (section == "python" && key == "path_append") {
print "python_path_append\t" value
} else if (section == "python" && key == "toplevel") {
print "python_toplevel\t" value
}
}
' "$file"
}
read_loadusr_commands() {
local file=$1
awk '
function trim(value) {
sub(/^[[:space:]]*/, "", value)
sub(/[[:space:]]*$/, "", value)
return value
}
function loadusr_program(value, fields, field_count, i, token) {
sub(/^.*(^|[[:space:]])loadusr[[:space:]]+/, "", value)
field_count = split(value, fields, /[[:space:]]+/)
for (i = 1; i <= field_count; i++) {
token = fields[i]
if (token == "-W" || token == "-w" || token == "-i") {
continue
}
if (token == "-Wn" || token == "-n") {
i++
continue
}
if (token ~ /^-Wn.+/ || token ~ /^-n.+/) {
continue
}
if (token ~ /^-[wWi]+$/) {
continue
}
if (token ~ /^-/) {
continue
}
return token
}
return ""
}
{
line = $0
sub(/[[:space:]]*#.*/, "", line)
line = trim(line)
lower = tolower(line)
if (lower !~ /(^|[[:space:]])loadusr([[:space:]]|$)/) {
next
}
command = loadusr_program(line)
if (command != "") {
print command
} }
} }
' "$file" ' "$file"
@@ -149,8 +266,14 @@ declare -A manifest_config_sources=()
declare -A used_config_sources=() declare -A used_config_sources=()
declare -A manifest_remap_sources=() declare -A manifest_remap_sources=()
declare -A used_remap_sources=() declare -A used_remap_sources=()
declare -A manifest_tooldata_sources=()
declare -A used_tooldata_sources=()
declare -A manifest_asset_sources=()
declare -A used_asset_sources=()
manifest_config_count=0 manifest_config_count=0
manifest_remap_count=0 manifest_remap_count=0
manifest_tooldata_count=0
manifest_asset_count=0
validate_manifest_source_path() { validate_manifest_source_path() {
local group=$1 local group=$1
@@ -205,6 +328,28 @@ while IFS=: read -r manifest_group manifest_path _manifest_note; do
manifest_remap_sources[$manifest_path]=1 manifest_remap_sources[$manifest_path]=1
manifest_remap_count=$((manifest_remap_count + 1)) manifest_remap_count=$((manifest_remap_count + 1))
;; ;;
tooldata)
validate_manifest_source_path "$manifest_group" "$manifest_path"
if [[ "$manifest_path" != *.tbl ]]; then
echo "LinuxCNC switchkins tooldata source must be a tbl file in $manifest: $manifest_path" >&2
exit 1
fi
if [[ -n "${manifest_tooldata_sources[$manifest_path]:-}" ]]; then
echo "duplicate LinuxCNC switchkins tooldata source in $manifest: $manifest_path" >&2
exit 1
fi
manifest_tooldata_sources[$manifest_path]=1
manifest_tooldata_count=$((manifest_tooldata_count + 1))
;;
asset)
validate_manifest_source_path "$manifest_group" "$manifest_path"
if [[ -n "${manifest_asset_sources[$manifest_path]:-}" ]]; then
echo "duplicate LinuxCNC asset source in $manifest: $manifest_path" >&2
exit 1
fi
manifest_asset_sources[$manifest_path]=1
manifest_asset_count=$((manifest_asset_count + 1))
;;
esac esac
done <"$manifest" done <"$manifest"
@@ -280,6 +425,172 @@ config_source_for_halfile() {
resolved_config_source=$resolved_linuxcnc_path resolved_config_source=$resolved_linuxcnc_path
} }
resolved_tooldata_source=
tooldata_source_for_tool_table() {
local config=$1
local tool_table=$2
resolved_tooldata_source=
[[ -n "$tool_table" ]] || return 1
local tool_path=${tool_table%%[[:space:]]*}
[[ -n "$tool_path" ]] || return 1
local config_dir
config_dir_for "$config"
config_dir=$resolved_config_dir
local resolved
resolve_config_path "$config_dir" "$tool_path"
resolved=$resolved_config_path
[[ -f "$resolved" ]] || return 1
relative_linuxcnc_path "$resolved"
resolved_tooldata_source=$resolved_linuxcnc_path
}
resolved_asset_source=
asset_source_for_path() {
local config=$1
local path=$2
resolved_asset_source=
[[ -n "$path" ]] || return 1
local config_dir
config_dir_for "$config"
config_dir=$resolved_config_dir
local resolved
resolve_config_path "$config_dir" "$path"
resolved=$resolved_config_path
[[ -f "$resolved" ]] || return 1
relative_linuxcnc_path "$resolved"
resolved_asset_source=$resolved_linuxcnc_path
}
resolved_loadusr_asset_source=
loadusr_asset_source_for_command() {
local config=$1
local command=$2
resolved_loadusr_asset_source=
[[ -n "$command" ]] || return 1
if [[ "$command" == */* || "$command" == *.py ]]; then
if asset_source_for_path "$config" "$command"; then
resolved_loadusr_asset_source=$resolved_asset_source
return 0
fi
if [[ "$command" != *.py ]] && asset_source_for_path "$config" "$command.py"; then
resolved_loadusr_asset_source=$resolved_asset_source
return 0
fi
return 1
fi
local source="src/hal/user_comps/vismach/$command.py"
if [[ -f "$linuxcnc_root/$source" ]]; then
resolved_loadusr_asset_source=$source
return 0
fi
if asset_source_for_path "$config" "$command.py"; then
resolved_loadusr_asset_source=$resolved_asset_source
return 0
fi
return 1
}
record_loadusr_asset() {
local config_path=$1
local config=$2
local command=$3
local label=$4
if loadusr_asset_source_for_command "$config" "$command"; then
loadusr_asset_source=$resolved_loadusr_asset_source
record_asset_source "$config_path" "$loadusr_asset_source" "$label"
record_python_import_assets "$config_path" "$loadusr_asset_source"
fi
}
resolved_python_module_source=
python_module_source_for_name() {
local config=$1
local module=$2
local python_paths=${3:-}
resolved_python_module_source=
[[ -n "$module" ]] || return 1
[[ "$module" == *.* ]] && return 1
local module_path=${module//.//}
if [[ -z "$python_paths" ]]; then
python_paths="."
fi
local config_dir
config_dir_for "$config"
config_dir=$resolved_config_dir
local python_path
while IFS= read -r python_path; do
[[ -n "$python_path" ]] || continue
local path_part
IFS=: read -ra path_parts <<<"$python_path"
for path_part in "${path_parts[@]}"; do
[[ -n "$path_part" ]] || continue
local candidate_dir
resolve_config_path "$config_dir" "$path_part"
candidate_dir=$resolved_config_path
if [[ -f "$candidate_dir/$module_path.py" ]]; then
relative_linuxcnc_path "$candidate_dir/$module_path.py"
resolved_python_module_source=$resolved_linuxcnc_path
return 0
fi
done
done <<<"$python_paths"
return 1
}
record_asset_source() {
local config_path=$1
local source=$2
local label=$3
if [[ -z "${manifest_asset_sources[$source]:-}" ]]; then
echo "LinuxCNC $label asset source is not listed as asset in $manifest: $source ($config_path)" >&2
exit 1
fi
used_asset_sources[$source]=1
}
declare -A walked_python_import_assets=()
record_python_import_assets() {
local config_path=$1
local source=$2
local walk_key="$config_path|$source"
if [[ -n "${walked_python_import_assets[$walk_key]:-}" ]]; then
return 0
fi
walked_python_import_assets[$walk_key]=1
local source_dir=${source%/*}
local imported
while IFS= read -r imported; do
[[ -n "$imported" ]] || continue
local import_source="$source_dir/$imported.py"
if [[ -f "$linuxcnc_root/$import_source" ]]; then
record_asset_source "$config_path" "$import_source" "Python import"
record_python_import_assets "$config_path" "$import_source"
fi
done < <(awk '
/^[[:space:]]*import[[:space:]]+[A-Za-z_][A-Za-z0-9_]*([[:space:]]|$)/ {
value = $0
sub(/^[[:space:]]*import[[:space:]]+/, "", value)
sub(/[[:space:],].*$/, "", value)
print value
}
/^[[:space:]]*from[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]+import[[:space:]]+/ {
value = $0
sub(/^[[:space:]]*from[[:space:]]+/, "", value)
sub(/[[:space:]].*$/, "", value)
print value
}
' "$linuxcnc_root/$source")
}
resolved_kinstype= resolved_kinstype=
declare -A kinstype_cache=() declare -A kinstype_cache=()
kinstype_for_remap_source() { kinstype_for_remap_source() {
@@ -350,6 +661,16 @@ record_config_source() {
used_config_sources[$source]=1 used_config_sources[$source]=1
} }
record_tooldata_source() {
local config_path=$1
local source=$2
if [[ -z "${manifest_tooldata_sources[$source]:-}" ]]; then
echo "LinuxCNC TOOL_TABLE source is not listed as tooldata in $manifest: $source ($config_path)" >&2
exit 1
fi
used_tooldata_sources[$source]=1
}
declare -A seen_aliases=() declare -A seen_aliases=()
declare -A seen_cases=() declare -A seen_cases=()
declare -A seen_case_values=() declare -A seen_case_values=()
@@ -360,6 +681,7 @@ processed_config_count=0
resolved_remap_count=0 resolved_remap_count=0
unique_config_source_count=0 unique_config_source_count=0
unique_resolved_remap_count=0 unique_resolved_remap_count=0
unique_tooldata_source_count=0
add_alias() { add_alias() {
local alias=$1 local alias=$1
@@ -609,7 +931,14 @@ while IFS=: read -r group path _note; do
kinematics= kinematics=
halfiles= halfiles=
postgui_halfiles= postgui_halfiles=
tool_tables=
subroutine_paths= subroutine_paths=
remap_ngcs=
on_abort_ngcs=
python_paths=
python_toplevels=
python_remaps=
halcmd_loadusrs=
remap_ngc_428= remap_ngc_428=
remap_ngc_429= remap_ngc_429=
remap_ngc_430= remap_ngc_430=
@@ -627,9 +956,30 @@ while IFS=: read -r group path _note; do
postgui_halfile) postgui_halfile)
postgui_halfiles+="${postgui_halfiles:+$'\n'}$ini_value" postgui_halfiles+="${postgui_halfiles:+$'\n'}$ini_value"
;; ;;
tool_table)
tool_tables+="${tool_tables:+$'\n'}$ini_value"
;;
subroutine_path) subroutine_path)
subroutine_paths+="${subroutine_paths:+$'\n'}$ini_value" subroutine_paths+="${subroutine_paths:+$'\n'}$ini_value"
;; ;;
remap_ngc)
remap_ngcs+="${remap_ngcs:+$'\n'}$ini_value"
;;
on_abort_ngc)
on_abort_ngcs+="${on_abort_ngcs:+$'\n'}$ini_value"
;;
python_path_append)
python_paths+="${python_paths:+$'\n'}$ini_value"
;;
python_toplevel)
python_toplevels+="${python_toplevels:+$'\n'}$ini_value"
;;
python_remap)
python_remaps+="${python_remaps:+$'\n'}$ini_value"
;;
halcmd_loadusr)
halcmd_loadusrs+="${halcmd_loadusrs:+$'\n'}$ini_value"
;;
remap_428) remap_428)
[[ -n "$remap_ngc_428" ]] || remap_ngc_428=$ini_value [[ -n "$remap_ngc_428" ]] || remap_ngc_428=$ini_value
;; ;;
@@ -723,6 +1073,10 @@ while IFS=: read -r group path _note; do
record_config_source "$path" "$hal_source" "HALFILE" record_config_source "$path" "$hal_source" "HALFILE"
add_alias "$hal_source" "$m428" "$m429" "$m430" add_alias "$hal_source" "$m428" "$m429" "$m430"
add_halfile_source_cases "$hal_source" "$m428" "$m429" "$m430" add_halfile_source_cases "$hal_source" "$m428" "$m429" "$m430"
while IFS= read -r loadusr_command; do
[[ -n "$loadusr_command" ]] || continue
record_loadusr_asset "$path" "$linuxcnc_root/$hal_source" "$loadusr_command" "HALFILE loadusr"
done < <(read_loadusr_commands "$linuxcnc_root/$hal_source")
fi fi
add_halfile_cases "$halfile" "$m428" "$m429" "$m430" add_halfile_cases "$halfile" "$m428" "$m429" "$m430"
add_halfile_aliases "$halfile" "$m428" "$m429" "$m430" add_halfile_aliases "$halfile" "$m428" "$m429" "$m430"
@@ -733,10 +1087,53 @@ while IFS=: read -r group path _note; do
record_config_source "$path" "$hal_source" "POSTGUI_HALFILE" record_config_source "$path" "$hal_source" "POSTGUI_HALFILE"
add_alias "$hal_source" "$m428" "$m429" "$m430" add_alias "$hal_source" "$m428" "$m429" "$m430"
add_postgui_halfile_source_cases "$hal_source" "$m428" "$m429" "$m430" add_postgui_halfile_source_cases "$hal_source" "$m428" "$m429" "$m430"
while IFS= read -r loadusr_command; do
[[ -n "$loadusr_command" ]] || continue
record_loadusr_asset "$path" "$linuxcnc_root/$hal_source" "$loadusr_command" "POSTGUI_HALFILE loadusr"
done < <(read_loadusr_commands "$linuxcnc_root/$hal_source")
fi fi
add_postgui_halfile_cases "$halfile" "$m428" "$m429" "$m430" add_postgui_halfile_cases "$halfile" "$m428" "$m429" "$m430"
add_halfile_aliases "$halfile" "$m428" "$m429" "$m430" add_halfile_aliases "$halfile" "$m428" "$m429" "$m430"
done <<<"$postgui_halfiles" done <<<"$postgui_halfiles"
while IFS= read -r tool_table; do
if tooldata_source_for_tool_table "$config" "$tool_table"; then
record_tooldata_source "$path" "$resolved_tooldata_source"
fi
done <<<"$tool_tables"
while IFS= read -r ngc_name; do
[[ -n "$ngc_name" ]] || continue
if remap_source_for_ngc "$config" "$ngc_name" "$subroutine_paths"; then
if [[ -z "${manifest_remap_sources[$resolved_remap_source]:-}" ]]; then
record_asset_source "$path" "$resolved_remap_source" "RS274NGC REMAP ngc"
fi
fi
done <<<"$remap_ngcs"
while IFS= read -r ngc_name; do
[[ -n "$ngc_name" ]] || continue
if remap_source_for_ngc "$config" "$ngc_name" "$subroutine_paths"; then
record_asset_source "$path" "$resolved_remap_source" "ON_ABORT_COMMAND"
fi
done <<<"$on_abort_ngcs"
while IFS= read -r toplevel; do
[[ -n "$toplevel" ]] || continue
if asset_source_for_path "$config" "$toplevel"; then
python_toplevel_source=$resolved_asset_source
record_asset_source "$path" "$python_toplevel_source" "PYTHON TOPLEVEL"
record_python_import_assets "$path" "$python_toplevel_source"
fi
done <<<"$python_toplevels"
while IFS= read -r python_remap; do
[[ -n "$python_remap" ]] || continue
if python_module_source_for_name "$config" "$python_remap" "$python_paths"; then
python_remap_source=$resolved_python_module_source
record_asset_source "$path" "$python_remap_source" "Python REMAP module"
record_python_import_assets "$path" "$python_remap_source"
fi
done <<<"$python_remaps"
while IFS= read -r loadusr_command; do
[[ -n "$loadusr_command" ]] || continue
record_loadusr_asset "$path" "$config" "$loadusr_command" "HALCMD loadusr"
done <<<"$halcmd_loadusrs"
for remap_source in "$remap_source_428" "$remap_source_429" "$remap_source_430"; do for remap_source in "$remap_source_428" "$remap_source_429" "$remap_source_430"; do
if [[ -n "$remap_source" ]]; then if [[ -n "$remap_source" ]]; then
@@ -758,6 +1155,8 @@ done <"$manifest"
unique_config_source_count=${#used_config_sources[@]} unique_config_source_count=${#used_config_sources[@]}
unique_resolved_remap_count=${#used_remap_sources[@]} unique_resolved_remap_count=${#used_remap_sources[@]}
unique_tooldata_source_count=${#used_tooldata_sources[@]}
unique_asset_source_count=${#used_asset_sources[@]}
validate_generated_content() { validate_generated_content() {
if ((manifest_config_count == 0)); then if ((manifest_config_count == 0)); then
@@ -784,6 +1183,10 @@ validate_generated_content() {
echo "no unique LinuxCNC config sources used from manifest: $manifest" >&2 echo "no unique LinuxCNC config sources used from manifest: $manifest" >&2
exit 1 exit 1
fi fi
if ((manifest_tooldata_count > 0 && unique_tooldata_source_count == 0)); then
echo "no unique LinuxCNC TOOL_TABLE sources used from manifest: $manifest" >&2
exit 1
fi
if ((unique_config_source_count != manifest_config_count)); then if ((unique_config_source_count != manifest_config_count)); then
echo "LinuxCNC config manifest coverage mismatch: used $unique_config_source_count of $manifest_config_count config sources" >&2 echo "LinuxCNC config manifest coverage mismatch: used $unique_config_source_count of $manifest_config_count config sources" >&2
local config_source local config_source
@@ -794,6 +1197,20 @@ validate_generated_content() {
done done
exit 1 exit 1
fi fi
if ((unique_tooldata_source_count != manifest_tooldata_count)); then
echo "LinuxCNC tooldata manifest coverage mismatch: used $unique_tooldata_source_count of $manifest_tooldata_count tooldata sources" >&2
local tooldata_source
for tooldata_source in "${!manifest_tooldata_sources[@]}"; do
if [[ -z "${used_tooldata_sources[$tooldata_source]:-}" ]]; then
echo "unused LinuxCNC tooldata manifest source: $tooldata_source" >&2
fi
done
exit 1
fi
if ((unique_asset_source_count > manifest_asset_count)); then
echo "LinuxCNC asset manifest coverage mismatch: used $unique_asset_source_count of $manifest_asset_count asset sources" >&2
exit 1
fi
if ((unique_resolved_remap_count != manifest_remap_count)); then if ((unique_resolved_remap_count != manifest_remap_count)); then
echo "LinuxCNC remap manifest coverage mismatch: resolved $unique_resolved_remap_count of $manifest_remap_count remap sources" >&2 echo "LinuxCNC remap manifest coverage mismatch: resolved $unique_resolved_remap_count of $manifest_remap_count remap sources" >&2
local remap_source local remap_source
@@ -875,6 +1292,11 @@ elif [[ "$mode" == all ]]; then
exit 1 exit 1
fi fi
mkdir -p "$all_output_dir" mkdir -p "$all_output_dir"
all_output_dir=$(cd "$all_output_dir" && pwd)
if [[ "$all_output_dir" == "/" ]]; then
echo "switchkins remap output directory must not be the filesystem root" >&2
exit 1
fi
write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_table.inc" "table" write_table write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_table.inc" "table" write_table
write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_config_cases.inc" "config cases" write_config_cases write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_config_cases.inc" "config cases" write_config_cases
write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_config_cases.json" "JSON config cases" write_json_cases write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_config_cases.json" "JSON config cases" write_json_cases

View File

@@ -277,9 +277,15 @@ metadata:docs/man/man9/tripodkins.9:LinuxCNC tripodkins generated manpage source
metadata:src/objects/hal/components/gantry.mak:halcompile-generated gantry component make metadata metadata:src/objects/hal/components/gantry.mak:halcompile-generated gantry component make metadata
metadata:src/objects/man/man9/gantry.9.adoc:halcompile-generated gantry component manpage source metadata:src/objects/man/man9/gantry.9.adoc:halcompile-generated gantry component manpage source
metadata:src/objects/hal/components/matrixkins.mak:halcompile-generated matrixkins make metadata adjacent to generated kinematics source metadata:src/objects/hal/components/matrixkins.mak:halcompile-generated matrixkins make metadata adjacent to generated kinematics source
metadata:src/objects/man/man9/matrixkins.9.adoc:halcompile-generated matrixkins manpage source adjacent to generated kinematics source
metadata:src/objects/hal/components/millturn.mak:halcompile-generated millturn make metadata adjacent to generated kinematics source metadata:src/objects/hal/components/millturn.mak:halcompile-generated millturn make metadata adjacent to generated kinematics source
metadata:src/objects/man/man9/millturn.9.adoc:halcompile-generated millturn manpage source adjacent to generated kinematics source
metadata:src/objects/hal/components/userkins.mak:halcompile-generated userkins make metadata adjacent to generated kinematics source metadata:src/objects/hal/components/userkins.mak:halcompile-generated userkins make metadata adjacent to generated kinematics source
metadata:src/objects/man/man9/userkins.9.adoc:halcompile-generated userkins manpage source adjacent to generated kinematics source
metadata:src/objects/hal/components/xyzab_tdr_kins.mak:halcompile-generated XYZAB table dual rotary make metadata adjacent to generated kinematics source metadata:src/objects/hal/components/xyzab_tdr_kins.mak:halcompile-generated XYZAB table dual rotary make metadata adjacent to generated kinematics source
metadata:src/objects/man/man9/xyzab_tdr_kins.9.adoc:halcompile-generated XYZAB table dual rotary manpage source adjacent to generated kinematics source
metadata:src/objects/hal/components/xyzacb_trsrn.mak:halcompile-generated XYZACB TRSRN make metadata adjacent to generated kinematics source metadata:src/objects/hal/components/xyzacb_trsrn.mak:halcompile-generated XYZACB TRSRN make metadata adjacent to generated kinematics source
metadata:src/objects/man/man9/xyzacb_trsrn.9.adoc:halcompile-generated XYZACB TRSRN manpage source adjacent to generated kinematics source
metadata:src/objects/hal/components/xyzbca_trsrn.mak:halcompile-generated XYZBCA TRSRN make metadata adjacent to generated kinematics source metadata:src/objects/hal/components/xyzbca_trsrn.mak:halcompile-generated XYZBCA TRSRN make metadata adjacent to generated kinematics source
metadata:src/objects/man/man9/xyzbca_trsrn.9.adoc:halcompile-generated XYZBCA TRSRN manpage source adjacent to generated kinematics source
asset:src/emc/kinematics/blend.fig:LinuxCNC kinematics reference asset tracked for full directory coverage asset:src/emc/kinematics/blend.fig:LinuxCNC kinematics reference asset tracked for full directory coverage

View File

@@ -3,6 +3,10 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: combined CMake source-set listers expose project
# bridge/shim/adapter sources declared in core/CMakeLists.txt so LinuxCNC
# source-linked probes can verify their thin wrapper objects without inventing
# G-code, canon, or kinematics behavior.
if [[ "$#" -lt 2 || "$#" -gt 3 ]]; then if [[ "$#" -lt 2 || "$#" -gt 3 ]]; then
echo "usage: $0 <set-name> <append-name> [relative-prefix]" >&2 echo "usage: $0 <set-name> <append-name> [relative-prefix]" >&2
exit 1 exit 1
@@ -11,9 +15,22 @@ fi
set_name=$1 set_name=$1
append_name=$2 append_name=$2
relative_prefix=${3:-} relative_prefix=${3:-}
if ! [[ "$set_name" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
echo "CMake source set name must be a CMake variable name: $set_name" >&2
exit 1
fi
if ! [[ "$append_name" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
echo "CMake append source set name must be a CMake variable name: $append_name" >&2
exit 1
fi
if [[ "$relative_prefix" = /* || "$relative_prefix" == .. || "$relative_prefix" == ../* || "$relative_prefix" == */.. || "$relative_prefix" == */../* ]]; then
echo "CMake source relative prefix must stay project-relative: $relative_prefix" >&2
exit 1
fi
awk -v set_name="$set_name" -v append_name="$append_name" -v relative_prefix="$relative_prefix" ' awk -v set_name="$set_name" -v append_name="$append_name" -v relative_prefix="$relative_prefix" '
$0 ~ "^[[:space:]]*set\\(" set_name "([[:space:]]|$)" { $0 ~ "^[[:space:]]*set\\(" set_name "([[:space:]]|$)" {
found_set = 1
in_set = 1 in_set = 1
next next
} }
@@ -22,6 +39,7 @@ awk -v set_name="$set_name" -v append_name="$append_name" -v relative_prefix="$r
next next
} }
$0 ~ "^[[:space:]]*list\\(APPEND " append_name "([[:space:]]|$)" { $0 ~ "^[[:space:]]*list\\(APPEND " append_name "([[:space:]]|$)" {
found_append = 1
in_append = 1 in_append = 1
next next
} }
@@ -42,4 +60,14 @@ awk -v set_name="$set_name" -v append_name="$append_name" -v relative_prefix="$r
} }
print line print line
} }
END {
if (!found_set) {
print "missing CMake source set: " set_name > "/dev/stderr"
exit 1
}
if (!found_append) {
print "missing CMake append source set: " append_name > "/dev/stderr"
exit 1
}
}
' core/CMakeLists.txt ' core/CMakeLists.txt

View File

@@ -3,6 +3,10 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: CMake source-set listers expose only project bridge,
# shim, and adapter files declared in core/CMakeLists.txt for source-backed
# LinuxCNC probe builds; functional CNC behavior still comes from LinuxCNC
# manifests or wrapped LinuxCNC kinematics/canon/RS274 sources.
if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then
echo "usage: $0 <cmake-set-name> [relative-prefix]" >&2 echo "usage: $0 <cmake-set-name> [relative-prefix]" >&2
exit 1 exit 1
@@ -10,9 +14,18 @@ fi
set_name=$1 set_name=$1
relative_prefix=${2:-} relative_prefix=${2:-}
if ! [[ "$set_name" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
echo "CMake source set name must be a CMake variable name: $set_name" >&2
exit 1
fi
if [[ "$relative_prefix" = /* || "$relative_prefix" == .. || "$relative_prefix" == ../* || "$relative_prefix" == */.. || "$relative_prefix" == */../* ]]; then
echo "CMake source relative prefix must stay project-relative: $relative_prefix" >&2
exit 1
fi
awk -v set_name="$set_name" -v relative_prefix="$relative_prefix" ' awk -v set_name="$set_name" -v relative_prefix="$relative_prefix" '
$0 ~ "^[[:space:]]*set\\(" set_name "([[:space:]]|$)" { $0 ~ "^[[:space:]]*set\\(" set_name "([[:space:]]|$)" {
found_set = 1
in_list = 1 in_list = 1
next next
} }
@@ -32,4 +45,10 @@ awk -v set_name="$set_name" -v relative_prefix="$relative_prefix" '
} }
print line print line
} }
END {
if (!found_set) {
print "missing CMake source set: " set_name > "/dev/stderr"
exit 1
}
}
' core/CMakeLists.txt ' core/CMakeLists.txt

View File

@@ -3,6 +3,10 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: native core smoke builds enumerate CMake-listed bridge
# and kinematics adapter sources that wrap LinuxCNC canon.hh and
# src/emc/kinematics behavior; the legacy smoke backend is included only when
# explicitly requested for API harness coverage.
include_smoke=0 include_smoke=0
filter= filter=
for arg in "$@"; do for arg in "$@"; do
@@ -34,6 +38,7 @@ trap 'rm -f "$source_list"' EXIT
} > "$source_list" } > "$source_list"
./list-filtered-existing-paths.sh \ ./list-filtered-existing-paths.sh \
--project-relative \
"$filter" \ "$filter" \
"missing cnc sim core source" \ "missing cnc sim core source" \
"missing cnc sim core source in CMake list" < "$source_list" "missing cnc sim core source in CMake list" < "$source_list"

View File

@@ -3,8 +3,17 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: source-list filters validate project bridge/shim
# paths before native and wasm probes link them with LinuxCNC source objects,
# keeping missing adapter files from silently dropping LinuxCNC-backed checks.
project_relative=0
if [[ "${1:-}" == "--project-relative" ]]; then
project_relative=1
shift
fi
if [[ "$#" -lt 3 ]]; then if [[ "$#" -lt 3 ]]; then
echo "usage: $0 <filter> <missing-path-message> <missing-filter-message> [missing-path-hint]" >&2 echo "usage: $0 [--project-relative] <filter> <missing-path-message> <missing-filter-message> [missing-path-hint]" >&2
exit 1 exit 1
fi fi
@@ -12,10 +21,19 @@ filter=$1
missing_path_message=$2 missing_path_message=$2
missing_filter_message=$3 missing_filter_message=$3
missing_path_hint=${4:-} missing_path_hint=${4:-}
if [[ -n "$filter" && ( "$filter" == */* || "$filter" == "." || "$filter" == ".." ) ]]; then
echo "source filter must be a basename: $filter" >&2
exit 1
fi
found=0 found=0
while IFS= read -r path; do while IFS= read -r path; do
[[ -z "$path" ]] && continue [[ -z "$path" ]] && continue
if [[ "$project_relative" -eq 1 &&
( "$path" = /* || "$path" == .. || "$path" == ../* || "$path" == */.. || "$path" == */../* ) ]]; then
echo "project source path must stay project-relative: $path" >&2
exit 1
fi
if [[ ! -f "$path" ]]; then if [[ ! -f "$path" ]]; then
echo "$missing_path_message: $path" >&2 echo "$missing_path_message: $path" >&2
if [[ -n "$missing_path_hint" ]]; then if [[ -n "$missing_path_hint" ]]; then

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: the bridge smoke source list is limited to the
# canon bridge and kinematics adapters that wrap LinuxCNC canon.hh and
# src/emc/kinematics behavior for native bridge checks.
filter=${1:-} filter=${1:-}
./list-project-source-set.sh linuxcnc-bridge-smoke "$filter" ./list-project-source-set.sh linuxcnc-bridge-smoke "$filter"

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: linuxcnc-kinematics-source-files.txt tracks
# src/emc/kinematics plus configs/sim INI/HAL/remap/tooldata/assets used by
# LinuxCNC switchkins, M428/M429/M430 remaps, and RTCP coverage.
manifest=${1:-linuxcnc-kinematics-source-files.txt} manifest=${1:-linuxcnc-kinematics-source-files.txt}
filter_group=${2:-core} filter_group=${2:-core}
output_mode=${3:-relative} output_mode=${3:-relative}

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: manifest listers expose LinuxCNC-root relative source
# entries from RS274, kinematics, remap, config, tooldata, and metadata
# manifests after check-linuxcnc-inputs.sh validates their source coverage.
manifest=${1:?manifest required} manifest=${1:?manifest required}
filter_group=${2:?filter group required} filter_group=${2:?filter group required}
output_mode=${3:?output mode required} output_mode=${3:?output mode required}
@@ -26,6 +29,11 @@ checks_duplicate_group() {
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest" LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest"
linuxcnc_root=$(cd "$linuxcnc_root" && pwd) linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
if [[ "$filter_group" != "all" && ! "$filter_group" =~ ^[A-Za-z0-9_-]+$ ]]; then
echo "manifest source filter must be a manifest group name: $filter_group" >&2
exit 1
fi
if [[ "$filter_group" != "all" ]] && ! contains_group "$allowed_groups" "$filter_group"; then if [[ "$filter_group" != "all" ]] && ! contains_group "$allowed_groups" "$filter_group"; then
echo "unknown manifest source filter: $filter_group" >&2 echo "unknown manifest source filter: $filter_group" >&2
exit 1 exit 1
@@ -41,17 +49,28 @@ case "$output_mode" in
esac esac
seen_sources=() seen_sources=()
while IFS=: read -r group path note; do manifest_line_number=0
case "$group" in while IFS= read -r manifest_line || [[ -n "$manifest_line" ]]; do
manifest_line_number=$((manifest_line_number + 1))
case "$manifest_line" in
""|\#*) ""|\#*)
continue continue
;; ;;
esac esac
IFS=: read -r group path note extra <<<"$manifest_line"
if [[ -z "$group" || -z "$path" || -z "$note" || -n "${extra:-}" ]]; then
echo "bad manifest line $manifest_line_number: $manifest_line" >&2
exit 1
fi
if ! contains_group "$allowed_groups" "$group"; then if ! contains_group "$allowed_groups" "$group"; then
echo "unknown manifest group: $group" >&2 echo "unknown manifest group: $group" >&2
exit 1 exit 1
fi fi
if [[ "$path" = /* || "$path" == .. || "$path" == ../* || "$path" == */.. || "$path" == */../* ]]; then
echo "manifest source must be LinuxCNC-root relative: $path" >&2
exit 1
fi
full_path="$linuxcnc_root/$path" full_path="$linuxcnc_root/$path"
if [[ ! -f "$full_path" ]]; then if [[ ! -f "$full_path" ]]; then

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: native source-link probes link against the built
# LinuxCNC lib directory required by src/emc/rs274ngc/Submakefile, including
# librs274 dependencies on linuxcncini, pyplugin, linuxcnchal, and tooldata.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache} preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache}

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: the RS274 API source list combines CMake-listed core
# bridge sources with linuxcnc_rs274_backend.cpp, which drives LinuxCNC
# rs274ngc_pre.cc through the thin public C API.
filter=${1:-} filter=${1:-}
source_list=$(mktemp) source_list=$(mktemp)
trap 'rm -f "$source_list"' EXIT trap 'rm -f "$source_list"' EXIT
@@ -13,6 +16,7 @@ trap 'rm -f "$source_list"' EXIT
} > "$source_list" } > "$source_list"
./list-filtered-existing-paths.sh \ ./list-filtered-existing-paths.sh \
--project-relative \
"$filter" \ "$filter" \
"missing LinuxCNC rs274 API project source" \ "missing LinuxCNC rs274 API project source" \
"missing LinuxCNC rs274 API project source in CMake list" < "$source_list" "missing LinuxCNC rs274 API project source in CMake list" < "$source_list"

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: the RS274 dump probe pairs LinuxCNC interpreter
# sources with the canon bridge, tooldata fixture, and kinematics adapters
# needed to serialize LinuxCNC-backed canonical events.
filter=${1:-} filter=${1:-}
./list-project-source-set.sh linuxcnc-rs274-dump "$filter" ./list-project-source-set.sh linuxcnc-rs274-dump "$filter"

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: native RS274/source-link builds consume headers from
# src/Makefile SUBDIRS plus src/emc/rs274ngc/Submakefile, pythonplugin,
# nml_intf, motion, and include exports used by librs274 and its bindings.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache} preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache}
include_python=0 include_python=0

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: fixed-source probes resolve explicit LinuxCNC-root
# relative paths from src/emc/rs274ngc, src/emc/tooldata, and related source
# directories before compiling or linking source-backed bridge checks.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
missing_source_message="missing LinuxCNC source" missing_source_message="missing LinuxCNC source"
preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache} preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache}
@@ -39,6 +42,10 @@ if [[ "$#" -eq 0 ]]; then
fi fi
for source in "$@"; do for source in "$@"; do
if [[ "$source" = /* || "$source" == .. || "$source" == ../* || "$source" == */.. || "$source" == */../* ]]; then
echo "LinuxCNC source path must be root-relative: $source" >&2
exit 1
fi
full_source="$linuxcnc_root/$source" full_source="$linuxcnc_root/$source"
if [[ ! -f "$full_source" ]]; then if [[ ! -f "$full_source" ]]; then
echo "$missing_source_message: $source" >&2 echo "$missing_source_message: $source" >&2

View File

@@ -3,6 +3,10 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: wasm-safe probes compile the manifest-selected
# src/emc/rs274ngc, src/emc/ini, and src/emc/tooldata sources against
# browser shims, so include roots mirror LinuxCNC src/Makefile and the rs274,
# ini, and tooldata Submakefiles without linking native LinuxCNC libraries.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache} preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache}
std_flag=-std=c++17 std_flag=-std=c++17

View File

@@ -3,12 +3,17 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: wasm-safe project probe sources are the thin bridge
# and adapter sources listed in core/CMakeLists.txt, including the
# LinuxCNC-backed RS274 bridge that wraps rs274ngc_pre.cc, canon.hh, and
# tooldata.hh behavior without adding independent G-code semantics.
filter=${1:-} filter=${1:-}
{ {
./list-cmake-set-sources.sh cnc_sim_core_sources core/ ./list-cmake-set-sources.sh cnc_sim_core_sources core/
./list-cmake-set-sources.sh cnc_sim_linuxcnc_rs274_backend_sources core/ ./list-cmake-set-sources.sh cnc_sim_linuxcnc_rs274_backend_sources core/
} | ./list-filtered-existing-paths.sh \ } | ./list-filtered-existing-paths.sh \
--project-relative \
"$filter" \ "$filter" \
"missing wasm-safe project source" \ "missing wasm-safe project source" \
"missing wasm-safe project source in CMake list" "missing wasm-safe project source in CMake list"

View File

@@ -3,9 +3,14 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: wasm-safe shim probe sources are limited to the
# linuxcnc_wasm_safe_probe_shims CMake set, whose files each carry nearby
# source-basis comments for the LinuxCNC Python, dlopen, HAL, tooldata, NML,
# RTAPI, and Boost.Python edges they replace for browser builds.
filter=${1:-} filter=${1:-}
./list-cmake-set-sources.sh linuxcnc_wasm_safe_probe_shims | ./list-filtered-existing-paths.sh \ ./list-cmake-set-sources.sh linuxcnc_wasm_safe_probe_shims | ./list-filtered-existing-paths.sh \
--project-relative \
"$filter" \ "$filter" \
"missing wasm-safe shim source" \ "missing wasm-safe shim source" \
"missing wasm-safe shim in CMake list" "missing wasm-safe shim in CMake list"

View File

@@ -3,6 +3,10 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: these project source sets are thin bridge/adaptor
# objects used by LinuxCNC-backed native probes. They wrap canon.hh,
# rs274ngc_pre.cc, tooldata.hh, and src/emc/kinematics sources instead of
# providing independent G-code or kinematics behavior.
if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then
echo "usage: $0 <source-set> [filter]" >&2 echo "usage: $0 <source-set> [filter]" >&2
exit 1 exit 1
@@ -116,6 +120,7 @@ case "$source_set" in
esac esac
printf '%s\n' "${sources[@]}" | ./list-filtered-existing-paths.sh \ printf '%s\n' "${sources[@]}" | ./list-filtered-existing-paths.sh \
--project-relative \
"$filter" \ "$filter" \
"$missing_source_message" \ "$missing_source_message" \
"$missing_filter_message" "$missing_filter_message"

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: the native API smoke links the thin cnc_sim API bridge
# with LinuxCNC RS274 headers, native support libraries, and source-backed
# canon/kinematics adapters selected from core/CMakeLists.txt.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
cxx=${CXX:-g++} cxx=${CXX:-g++}
if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: the native bridge smoke links project canon/kinematics
# adapters against LinuxCNC headers and native support libraries to verify the
# wrapper ABI without adding independent RS274 or kinematics behavior.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
cxx=${CXX:-g++} cxx=${CXX:-g++}
if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -43,11 +43,13 @@ missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_m
missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest_source.XXXXXX.log") missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest_source.XXXXXX.log")
duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.txt") duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.txt")
duplicate_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.log") duplicate_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.log")
bad_line_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_bad_wasm_blocker_manifest_line.XXXXXX.txt")
bad_line_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_bad_wasm_blocker_manifest_line.XXXXXX.log")
missing_replacement_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.txt") missing_replacement_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.txt")
missing_replacement_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.log") missing_replacement_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.log")
trap 'rm -f "$missing_manifest" "$missing_manifest_log" "$missing_root_log" "$missing_source_manifest" "$missing_source_log" "$duplicate_manifest" "$duplicate_manifest_log" "$missing_replacement_manifest" "$missing_replacement_log"' EXIT trap 'rm -f "$missing_manifest" "$missing_manifest_log" "$missing_root_log" "$missing_source_manifest" "$missing_source_log" "$duplicate_manifest" "$duplicate_manifest_log" "$bad_line_manifest" "$bad_line_log" "$missing_replacement_manifest" "$missing_replacement_log"' EXIT
if ! awk ' if ! awk '
/^trap '\''rm -f "\$missing_manifest" "\$missing_manifest_log" "\$missing_root_log" "\$missing_source_manifest" "\$missing_source_log" "\$duplicate_manifest" "\$duplicate_manifest_log" "\$missing_replacement_manifest" "\$missing_replacement_log"'\'' EXIT$/ { found_trap = 1 } /^trap '\''rm -f "\$missing_manifest" "\$missing_manifest_log" "\$missing_root_log" "\$missing_source_manifest" "\$missing_source_log" "\$duplicate_manifest" "\$duplicate_manifest_log" "\$bad_line_manifest" "\$bad_line_log" "\$missing_replacement_manifest" "\$missing_replacement_log"'\'' EXIT$/ { found_trap = 1 }
END { exit !found_trap } END { exit !found_trap }
' test-linuxcnc-wasm-blockers.sh; then ' test-linuxcnc-wasm-blockers.sh; then
echo "wasm blocker scan cleanup trap no longer covers fixed blocker temporaries" >&2 echo "wasm blocker scan cleanup trap no longer covers fixed blocker temporaries" >&2
@@ -106,6 +108,17 @@ if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$dupli
exit 1 exit 1
fi fi
printf 'core:src/emc/rs274ngc/interp_arc.cc\n' >"$bad_line_manifest"
if ./analyze-linuxcnc-wasm-blockers.sh "$bad_line_manifest" >"$bad_line_log" 2>&1; then
echo "wasm blocker analyzer accepted bad manifest line" >&2
exit 1
fi
if ! grep -F "bad manifest line 1: core:src/emc/rs274ngc/interp_arc.cc" "$bad_line_log" >/dev/null; then
echo "wasm blocker analyzer did not report bad manifest line clearly" >&2
sed -n '1,20p' "$bad_line_log" >&2
exit 1
fi
cat >"$missing_replacement_manifest" <<'EOF' cat >"$missing_replacement_manifest" <<'EOF'
core:src/emc/rs274ngc/interp_arc.cc:core entry core:src/emc/rs274ngc/interp_arc.cc:core entry
blocked:src/emc/tooldata/tooldata_db.cc:blocked entry without wasm replacement blocked:src/emc/tooldata/tooldata_db.cc:blocked entry without wasm replacement
@@ -182,6 +195,14 @@ grep -F "src/emc/rs274ngc/gcodemodule.cc" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<
grep -F "src/emc/rs274ngc/interpmodule.cc" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<"$output")" >/dev/null grep -F "src/emc/rs274ngc/interpmodule.cc" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<"$output")" >/dev/null
grep -F "src/emc/rs274ngc/pyarrays.cc" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<"$output")" >/dev/null grep -F "src/emc/rs274ngc/pyarrays.cc" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<"$output")" >/dev/null
grep -F "src/emc/rs274ngc/pyparamclass.cc" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<"$output")" >/dev/null grep -F "src/emc/rs274ngc/pyparamclass.cc" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<"$output")" >/dev/null
grep -F "[python-compile-covered]" <<<"$output" >/dev/null
grep -F "src/emc/rs274ngc/canonmodule.cc" <<<"$(sed -n '/^\[python-compile-covered\]/,/^$/p' <<<"$output")" >/dev/null
grep -F "src/emc/rs274ngc/gcodemodule.cc" <<<"$(sed -n '/^\[python-compile-covered\]/,/^$/p' <<<"$output")" >/dev/null
grep -F "src/emc/rs274ngc/interpmodule.cc" <<<"$(sed -n '/^\[python-compile-covered\]/,/^$/p' <<<"$output")" >/dev/null
grep -F "src/emc/rs274ngc/pyarrays.cc" <<<"$(sed -n '/^\[python-compile-covered\]/,/^$/p' <<<"$output")" >/dev/null
grep -F "src/emc/rs274ngc/pyparamclass.cc" <<<"$(sed -n '/^\[python-compile-covered\]/,/^$/p' <<<"$output")" >/dev/null
grep -F 'python_compile_probe_covers "$path"' analyze-linuxcnc-wasm-blockers.sh >/dev/null
grep -F 'test-linuxcnc-wasm-python-c-api-symbols.sh' analyze-linuxcnc-wasm-blockers.sh >/dev/null
grep -F "[dlopen]" <<<"$output" >/dev/null grep -F "[dlopen]" <<<"$output" >/dev/null
grep -F "(none)" <<<"$(sed -n '/^\[dlopen\]/,/^$/p' <<<"$output")" >/dev/null grep -F "(none)" <<<"$(sed -n '/^\[dlopen\]/,/^$/p' <<<"$output")" >/dev/null
grep -F "[tooldata]" <<<"$output" >/dev/null grep -F "[tooldata]" <<<"$output" >/dev/null

View File

@@ -56,6 +56,8 @@ wasm_python_c_api_build_dir_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_python_c_
wasm_interp_find_build_dir_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_interp_find_build_dir.XXXXXX.log") wasm_interp_find_build_dir_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_interp_find_build_dir.XXXXXX.log")
wasm_tooldata_mmap_build_dir_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_tooldata_mmap_build_dir.XXXXXX.log") wasm_tooldata_mmap_build_dir_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_tooldata_mmap_build_dir.XXXXXX.log")
wasm_tooldata_runtime_build_dir_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_tooldata_runtime_build_dir.XXXXXX.log") wasm_tooldata_runtime_build_dir_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_tooldata_runtime_build_dir.XXXXXX.log")
smoke_wasm_probe_cmake_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_smoke_wasm_probe_cmake.XXXXXX")
smoke_wasm_probe_cmake_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_smoke_wasm_probe_cmake.XXXXXX.log")
cleanup_wasm_cmake_probe_temps() { cleanup_wasm_cmake_probe_temps() {
local temp_path local temp_path
@@ -76,6 +78,8 @@ cleanup_wasm_cmake_probe_temps() {
"$wasm_interp_find_build_dir_log" \ "$wasm_interp_find_build_dir_log" \
"$wasm_tooldata_mmap_build_dir_log" \ "$wasm_tooldata_mmap_build_dir_log" \
"$wasm_tooldata_runtime_build_dir_log" \ "$wasm_tooldata_runtime_build_dir_log" \
"$smoke_wasm_probe_cmake_dir" \
"$smoke_wasm_probe_cmake_log" \
"${build_next_signature:-}" \ "${build_next_signature:-}" \
"${unknown_manifest_filter_log:-}" \ "${unknown_manifest_filter_log:-}" \
"${unknown_manifest_output_log:-}" \ "${unknown_manifest_output_log:-}" \
@@ -536,6 +540,7 @@ if grep -F '} > "$report_file" || true' test-linuxcnc-wasm-rs274ngc-pre-link-blo
exit 1 exit 1
fi fi
grep -Fx 'keep_report_file=0' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null grep -Fx 'keep_report_file=0' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -F 'LinuxCNC source basis: this blocker scan inspects unresolved symbols from' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -Fx 'trap cleanup_rs274ngc_pre_blocker_temps EXIT' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null grep -Fx 'trap cleanup_rs274ngc_pre_blocker_temps EXIT' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -Fx 'keep_report_file=1' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null grep -Fx 'keep_report_file=1' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
if ! awk ' if ! awk '
@@ -557,7 +562,9 @@ if ! awk '
fi fi
grep -F 'CNC_SIM_WASM_RS274NGC_PRE_LINK_BLOCKERS_CACHE_DIR' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null grep -F 'CNC_SIM_WASM_RS274NGC_PRE_LINK_BLOCKERS_CACHE_DIR' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -F 'rs274ngc-pre-link-blockers.lock' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null grep -F 'rs274ngc-pre-link-blockers.lock' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -F "printf 'FILTER_RULE_VERSION=1\n'" test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null grep -F "printf 'FILTER_RULE_VERSION=2\n'" test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -F 'setenv|setlocale' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -F 'unlink|unsetenv|uselocale' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -F "stat -c 'OBJECT=%n:%s:%y'" test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null grep -F "stat -c 'OBJECT=%n:%s:%y'" test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -F 'cmp -s "$signature" "$next_signature"' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null grep -F 'cmp -s "$signature" "$next_signature"' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
for build_dir_probe_script in \ for build_dir_probe_script in \
@@ -573,7 +580,7 @@ for build_dir_probe_script in \
exit 1 exit 1
fi fi
done done
grep -F 'trap '\''rm -f "$missing_manifest" "$missing_manifest_log" "$missing_root_log" "$missing_source_manifest" "$missing_source_log" "$duplicate_manifest" "$duplicate_manifest_log" "$missing_replacement_manifest" "$missing_replacement_log"'\'' EXIT' test-linuxcnc-wasm-blockers.sh >/dev/null grep -F 'trap '\''rm -f "$missing_manifest" "$missing_manifest_log" "$missing_root_log" "$missing_source_manifest" "$missing_source_log" "$duplicate_manifest" "$duplicate_manifest_log" "$bad_line_manifest" "$bad_line_log" "$missing_replacement_manifest" "$missing_replacement_log"'\'' EXIT' test-linuxcnc-wasm-blockers.sh >/dev/null
grep -F 'self_checks=${CNC_SIM_WASM_BLOCKERS_SELF_CHECKS:-0}' test-linuxcnc-wasm-blockers.sh >/dev/null grep -F 'self_checks=${CNC_SIM_WASM_BLOCKERS_SELF_CHECKS:-0}' test-linuxcnc-wasm-blockers.sh >/dev/null
grep -F 'CNC_SIM_WASM_BLOCKERS_SELF_CHECKS must be 0 or 1: $self_checks' test-linuxcnc-wasm-blockers.sh >/dev/null grep -F 'CNC_SIM_WASM_BLOCKERS_SELF_CHECKS must be 0 or 1: $self_checks' test-linuxcnc-wasm-blockers.sh >/dev/null
if grep -F 'rm -f "$missing_source_manifest" "$missing_source_log"' test-linuxcnc-wasm-blockers.sh >/dev/null; then if grep -F 'rm -f "$missing_source_manifest" "$missing_source_log"' test-linuxcnc-wasm-blockers.sh >/dev/null; then
@@ -629,6 +636,7 @@ if grep -RInF "${forbidden_common_flags_process_substitution}LINUXCNC_ROOT=\"\$l
grep -RInF "${forbidden_common_flags_process_substitution}LINUXCNC_ROOT=\"\$linuxcnc_root\" ${forbidden_common_flags_helper}" test-linuxcnc-wasm-*.sh >&2 grep -RInF "${forbidden_common_flags_process_substitution}LINUXCNC_ROOT=\"\$linuxcnc_root\" ${forbidden_common_flags_helper}" test-linuxcnc-wasm-*.sh >&2
exit 1 exit 1
fi fi
grep -F 'LinuxCNC source basis: wasm-safe probes compile the manifest-selected' list-linuxcnc-wasm-common-cxxflags.sh >/dev/null
grep -F 'preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache}' list-linuxcnc-wasm-common-cxxflags.sh >/dev/null grep -F 'preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/linuxcnc-helper-preflight-cache}' list-linuxcnc-wasm-common-cxxflags.sh >/dev/null
grep -F 'check-linuxcnc-inputs-cached.sh --cache-dir "$preflight_cache_dir" --root-only' list-linuxcnc-wasm-common-cxxflags.sh >/dev/null grep -F 'check-linuxcnc-inputs-cached.sh --cache-dir "$preflight_cache_dir" --root-only' list-linuxcnc-wasm-common-cxxflags.sh >/dev/null
grep -F -- '-DCNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST="$manifest"' build-wasm.sh >/dev/null grep -F -- '-DCNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST="$manifest"' build-wasm.sh >/dev/null
@@ -644,7 +652,7 @@ grep -F 'wasm_build_signature=build/wasm/wasm-build.signature' build-wasm.sh >/d
grep -F 'wasm_build_next_signature=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_build.signature.XXXXXX")' build-wasm.sh >/dev/null grep -F 'wasm_build_next_signature=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_build.signature.XXXXXX")' build-wasm.sh >/dev/null
grep -F 'sha256sum "$manifest"' build-wasm.sh >/dev/null grep -F 'sha256sum "$manifest"' build-wasm.sh >/dev/null
grep -F 'sha256sum core/CMakeLists.txt' build-wasm.sh >/dev/null grep -F 'sha256sum core/CMakeLists.txt' build-wasm.sh >/dev/null
grep -F "printf 'CONFIGURE_RULE_VERSION=1\n'" build-wasm.sh >/dev/null grep -F "printf 'CONFIGURE_RULE_VERSION=2\n'" build-wasm.sh >/dev/null
grep -F 'run_parallel_wasm_probe() {' build-wasm.sh >/dev/null grep -F 'run_parallel_wasm_probe() {' build-wasm.sh >/dev/null
grep -F 'wait_parallel_wasm_probes() {' build-wasm.sh >/dev/null grep -F 'wait_parallel_wasm_probes() {' build-wasm.sh >/dev/null
grep -F 'parallel_probe_pids+=("$!")' build-wasm.sh >/dev/null grep -F 'parallel_probe_pids+=("$!")' build-wasm.sh >/dev/null
@@ -755,7 +763,7 @@ 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 -- './test-web-wasm-browser-smoke.sh' build-wasm.sh >/dev/null
grep -F 'const opfsOptions = options.opfs ?? (isOpfsAvailable() ? {} : false)' web/src/wasm-core.js >/dev/null grep -F 'const opfsOptions = options.opfs ?? (isOpfsAvailable() ? {} : false)' web/src/wasm-core.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 cannot be disabled in browser contexts with OPFS support' web/src/wasm-core.js >/dev/null
if grep -R -n -E 'localStorage|sessionStorage|indexedDB|showOpenFilePicker|showSaveFilePicker|webkitRequestFileSystem|FileReader' web/src; then 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 echo "browser app/wasm code must not add filesystem persistence outside OPFS" >&2
exit 1 exit 1
fi fi
@@ -770,28 +778,83 @@ grep -F 'exists(path: string): Promise<boolean>;' web/src/wasm-core.d.ts >/dev/n
grep -F 'stat(path: string): Promise<WasmOpfsEntryStat>;' web/src/wasm-core.d.ts >/dev/null grep -F 'stat(path: string): Promise<WasmOpfsEntryStat>;' web/src/wasm-core.d.ts >/dev/null
grep -F 'LinuxCNC source basis: rs274ngc_pre.cc restore_parameters() reads the' 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 'before replacing the main file and managing filename + ".bak".' web/src/wasm-core.js >/dev/null
grep -F 'await removeOpfsEntry(workspacePath, `${path}.new`, false);' web/src/wasm-core.js >/dev/null grep -F 'await removeOpfsFileEntry(workspacePath, `${path}.new`);' web/src/wasm-core.js >/dev/null
grep -F 'OPFS workspace is required for browser program parsing' web/src/app.js >/dev/null grep -F 'OPFS workspace is required for browser program parsing' 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 'const events = await simulator.parseFileWithParameterFile(programPath, parameterPath, "linuxcnc", parseOptions);' web/src/app.js >/dev/null
grep -F 'default browser simulator did not create an OPFS workspace' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null grep -F 'default browser simulator did not create an OPFS workspace' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
grep -F 'default browser simulator used an unexpected OPFS workspace path' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
grep -F 'browser simulator allowed OPFS to be disabled' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null grep -F 'browser simulator allowed OPFS to be disabled' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
grep -F 'browser simulator accepted unsafe OPFS workspace ${unsafeWorkspacePath}' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
grep -F 'browser simulator accepted unsafe OPFS mount ${unsafeMountPoint}' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
grep -F 'browser simulator accepted empty OPFS mount' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
grep -F 'expected OPFS workspace to be available in browser smoke' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'unexpected OPFS workspace path' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'default OPFS mount point was not preserved' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'OPFS ${operation} accepted an unsafe path' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'browser parseFile left LinuxCNC parameter state outside OPFS: ${parameterPath}' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'LinuxCNC OPFS program file was not restored across WASM simulator instances' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'missing expected LinuxCNC WASM browser linear-feed event' 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 'missing LinuxCNC WASM browser generated ${configCase.field}=${configCase.value} line ${line} switchkins type ${kinstype}' web/test-browser-wasm-smoke-linuxcnc-sections.js >/dev/null
grep -F 'missing LinuxCNC WASM browser M${427 + line} switchkins type ${kinstype}' web/test-browser-wasm-smoke-linuxcnc-sections.js >/dev/null
grep -F 'browser parse left LinuxCNC parameter state outside OPFS: ${parameterPath}' web/test-browser-wasm-smoke-linuxcnc-sections.js >/dev/null
grep -F 'browser smoke HTML must load /public/cnc_sim.js' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'browser smoke module script must import /src/wasm-core.js' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'browser smoke sections module must import {imported_module}' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'browser smoke OPFS workspace module must import {imported_module}' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'app frame did not load CNC controls' web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not create OPFS simulator workspace' web/test-browser-wasm-smoke-app-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 parse second program for parameter backup coverage' web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not replace LinuxCNC parameter state after second parse' 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 restore current program from OPFS after reload' web/test-browser-wasm-smoke-app-sections.js >/dev/null grep -F 'app frame did not restore current program from OPFS after reload' web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'app frame did not report OPFS program restore after reload' web/test-browser-wasm-smoke-app-sections.js >/dev/null grep -F 'app frame did not report OPFS program restore after reload' 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 state into OPFS' web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'restored app frame did not load CNC controls' web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'restored app frame did not create OPFS simulator workspace' web/test-browser-wasm-smoke-app-sections.js >/dev/null
grep -F 'restored app frame did not parse program with reloaded parameter state' 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 'app frame did not reload LinuxCNC parameter state from OPFS after reload' web/test-browser-wasm-smoke-app-sections.js >/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 'minimum_sections=${BROWSER_SMOKE_MIN_SECTIONS:-50}' 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 '"parameters/missing-main.var.new", "stale temporary without main file' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null grep -F '"parameters/missing-main.var.new", "stale temporary without main file' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'opfs persisted wasm mirror parse in new instance' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
grep -F 'persisted WASM mirror file was not restored from OPFS in a new instance' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
grep -F 'persisted WASM mirror file was not parsed through LinuxCNC in a new instance' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
grep -F 'OPFS copyFile did not create the backup file' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null grep -F 'OPFS copyFile did not create the backup file' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
grep -F 'OPFS copyFile did not update the WASM mirror destination' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null grep -F 'OPFS copyFile did not update the WASM mirror destination' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
grep -F 'OPFS moveFile did not replace the destination file' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null grep -F 'OPFS moveFile did not replace the destination file' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
grep -F 'OPFS moveFile left the WASM mirror source behind' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null grep -F 'OPFS moveFile left the WASM mirror source behind' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
grep -F 'unexpected OPFS directory persist file list' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'stale OPFS directory file remained after directory persist' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'removed WASM directory file remained in OPFS after directory persist' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'OPFS directory was not restored into a new WASM filesystem instance' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'OPFS metadata queries polluted the WASM filesystem mirror' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null grep -F 'OPFS metadata queries polluted the WASM filesystem mirror' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'OPFS persistDirectory did not preserve an empty directory' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null grep -F 'OPFS persistDirectory did not preserve an empty directory' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'OPFS readDirectory did not mirror an empty directory into WASM FS' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null grep -F 'OPFS readDirectory did not mirror an empty directory into WASM FS' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'OPFS readFile did not replace the WASM mirror directory with a file' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null grep -F 'OPFS readFile did not replace the WASM mirror directory with a file' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'cleared OPFS workspace still exposed a persisted directory' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'OPFS removeFile removed a directory instead of preserving file-only semantics' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'LinuxCNC parameter file was not persisted into OPFS' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC OPFS parameter file was not restored across WASM simulator instances' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC default OPFS parameter backup did not preserve the previous file' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC parameter backup file was not bridged into OPFS' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC existing OPFS parameter backup did not replace the stale backup with the previous file' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC missing parameter file was not created through OPFS' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC-created missing OPFS parameter file was not restored on the next parse' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'stale LinuxCNC missing-parameter temporary file remained in OPFS' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null grep -F 'stale LinuxCNC missing-parameter temporary file remained in OPFS' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'stale LinuxCNC missing-parameter temporary file remained in WASM FS' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null grep -F 'stale LinuxCNC missing-parameter temporary file remained in WASM FS' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC out-of-order parameter file remained in WASM FS after restore failure' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null grep -F 'LinuxCNC out-of-order parameter file remained in WASM FS after restore failure' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC out-of-range parameter file remained in WASM FS after restore failure' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null grep -F 'LinuxCNC out-of-range parameter file remained in WASM FS after restore failure' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC accepted a failing parse that should not persist parameters' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC persisted failed parse parameter updates into OPFS' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC accepted a failing parse that should not replace the parameter backup' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC replaced the OPFS parameter backup after a failed parse' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'LinuxCNC stale OPFS parameter state was not cleared when the file was missing' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'missing OPFS parameter file did not return null' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'missing OPFS parameter load left stale WASM file ${parameterPath}' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
if [[ "$(grep -Fc 'cp build/wasm/cnc_sim.js' build-wasm.sh)" -ne 1 ]]; then if [[ "$(grep -Fc 'cp build/wasm/cnc_sim.js' build-wasm.sh)" -ne 1 ]]; then
echo "build-wasm should copy cnc_sim.js exactly once" >&2 echo "build-wasm should copy cnc_sim.js exactly once" >&2
exit 1 exit 1
@@ -883,6 +946,15 @@ grep -Fx "src/emc/tooldata/tooldata_mmap.cc" <<<"$wasm_manifest_blocked" >/dev/n
wasm_manifest_blocked_header=$(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked-header) wasm_manifest_blocked_header=$(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked-header)
grep -Fx "src/emc/rs274ngc/boost_pyenum_macros.hh" <<<"$wasm_manifest_blocked_header" >/dev/null grep -Fx "src/emc/rs274ngc/boost_pyenum_macros.hh" <<<"$wasm_manifest_blocked_header" >/dev/null
unknown_manifest_filter_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_filter.XXXXXX.log") unknown_manifest_filter_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_filter.XXXXXX.log")
if ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core,header >"$unknown_manifest_filter_log" 2>&1; then
echo "wasm manifest source lister accepted a compound filter" >&2
exit 1
fi
if ! grep -F "manifest source filter must be a manifest group name: core,header" "$unknown_manifest_filter_log" >/dev/null; then
echo "wasm manifest source lister did not report compound filters clearly" >&2
sed -n '1,20p' "$unknown_manifest_filter_log" >&2
exit 1
fi
if ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" bogus >"$unknown_manifest_filter_log" 2>&1; then if ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" bogus >"$unknown_manifest_filter_log" 2>&1; then
echo "wasm manifest source lister accepted unknown filter" >&2 echo "wasm manifest source lister accepted unknown filter" >&2
exit 1 exit 1
@@ -1185,6 +1257,11 @@ cp check-linuxcnc-inputs-cached.sh "$bad_default_manifest_dir/check-linuxcnc-inp
cp list-linuxcnc-source-files.sh "$bad_default_manifest_dir/list-linuxcnc-source-files.sh" cp list-linuxcnc-source-files.sh "$bad_default_manifest_dir/list-linuxcnc-source-files.sh"
cp list-linuxcnc-manifest-sources.sh "$bad_default_manifest_dir/list-linuxcnc-manifest-sources.sh" cp list-linuxcnc-manifest-sources.sh "$bad_default_manifest_dir/list-linuxcnc-manifest-sources.sh"
cp list-linuxcnc-wasm-manifest-sources.sh "$bad_default_manifest_dir/list-linuxcnc-wasm-manifest-sources.sh" cp list-linuxcnc-wasm-manifest-sources.sh "$bad_default_manifest_dir/list-linuxcnc-wasm-manifest-sources.sh"
cat >"$bad_default_manifest_dir/check-linuxcnc-switchkins-remap-table-cached.sh" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
chmod +x "$bad_default_manifest_dir/check-linuxcnc-switchkins-remap-table-cached.sh"
printf 'core:src/emc/rs274ngc/interp_arc.cc:test bad default core count\nblocked:src/emc/tooldata/tooldata_mmap.cc:test bad default blocked count\n' \ printf 'core:src/emc/rs274ngc/interp_arc.cc:test bad default core count\nblocked:src/emc/tooldata/tooldata_mmap.cc:test bad default blocked count\n' \
>"$bad_default_manifest_dir/linuxcnc-rs274-wasm-source-files.txt" >"$bad_default_manifest_dir/linuxcnc-rs274-wasm-source-files.txt"
if CNC_SIM_BUILD_WASM_SKIP_LOCK=1 LINUXCNC_ROOT="$linuxcnc_root_abs" "$bad_default_manifest_dir/build-wasm.sh" >"$bad_default_manifest_log" 2>&1; then if CNC_SIM_BUILD_WASM_SKIP_LOCK=1 LINUXCNC_ROOT="$linuxcnc_root_abs" "$bad_default_manifest_dir/build-wasm.sh" >"$bad_default_manifest_log" 2>&1; then
@@ -1396,7 +1473,9 @@ awk '
grep -F "LinuxCNC wasm source manifest has no core entries" core/CMakeLists.txt >/dev/null grep -F "LinuxCNC wasm source manifest has no core entries" core/CMakeLists.txt >/dev/null
grep -F "LinuxCNC wasm source manifest has no blocked entries" core/CMakeLists.txt >/dev/null grep -F "LinuxCNC wasm source manifest has no blocked entries" core/CMakeLists.txt >/dev/null
grep -F "WASM builds must enable CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE so LinuxCNC rs274 sources are linked" core/CMakeLists.txt >/dev/null grep -F "WASM builds must enable CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE so LinuxCNC rs274 sources are linked" core/CMakeLists.txt >/dev/null
grep -F "CNC_SIM_ENABLE_SMOKE_BACKEND cannot be combined with CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE" core/CMakeLists.txt >/dev/null
grep -F -- '-DCNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON' 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 'if(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE)' core/CMakeLists.txt >/dev/null grep -F 'if(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE)' core/CMakeLists.txt >/dev/null
grep -F 'list(APPEND cnc_sim_core_sources ${cnc_sim_linuxcnc_rs274_backend_sources})' core/CMakeLists.txt >/dev/null grep -F 'list(APPEND cnc_sim_core_sources ${cnc_sim_linuxcnc_rs274_backend_sources})' core/CMakeLists.txt >/dev/null
grep -F 'set(cnc_sim_linuxcnc_kinematics_c_sources' core/CMakeLists.txt >/dev/null grep -F 'set(cnc_sim_linuxcnc_kinematics_c_sources' core/CMakeLists.txt >/dev/null
@@ -1452,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/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/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 '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 '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" 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 grep -F 'extern "C" void wordfree(wordexp_t *pwordexp)' core/wasm_shims/linuxcnc_runtime_shim.cc >/dev/null
@@ -1466,6 +1549,24 @@ grep -F '$<TARGET_OBJECTS:linuxcnc_rs274_wasm_safe_probe_objects>' core/CMakeLis
grep -F '$<TARGET_OBJECTS:cnc_sim_objects>' core/CMakeLists.txt >/dev/null grep -F '$<TARGET_OBJECTS:cnc_sim_objects>' core/CMakeLists.txt >/dev/null
grep -F 'CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND' core/CMakeLists.txt >/dev/null grep -F 'CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND' core/CMakeLists.txt >/dev/null
grep -F '#ifndef CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE' core/src/linuxcnc_rs274_backend.cpp >/dev/null grep -F '#ifndef CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE' core/src/linuxcnc_rs274_backend.cpp >/dev/null
if command -v cmake >/dev/null 2>&1; then
if cmake -S core -B "$smoke_wasm_probe_cmake_dir" \
-DCMAKE_BUILD_TYPE=Release \
-DCNC_SIM_LINUXCNC_ROOT="$linuxcnc_root" \
-DCNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST="$manifest" \
-DCNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON \
-DCNC_SIM_ENABLE_SMOKE_BACKEND=ON \
>"$smoke_wasm_probe_cmake_log" 2>&1; then
echo "CMake accepted legacy smoke backend with LinuxCNC wasm-safe probe enabled" >&2
exit 1
fi
if ! grep -F "CNC_SIM_ENABLE_SMOKE_BACKEND cannot be combined with" "$smoke_wasm_probe_cmake_log" >/dev/null ||
! grep -F "CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE" "$smoke_wasm_probe_cmake_log" >/dev/null; then
echo "CMake did not report smoke/wasm-safe probe conflict clearly" >&2
sed -n '1,40p' "$smoke_wasm_probe_cmake_log" >&2
exit 1
fi
fi
fi fi

View File

@@ -7,6 +7,10 @@ cd "$(dirname "$0")"
# whose runtime dynamic loading edge is satisfied by the wasm-safe dlfcn shim. # 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=$(./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" echo "linuxcnc wasm interp_base link probe passed"

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: src/emc/rs274ngc/canonmodule.cc, interpmodule.cc,
# and py*.cc are blocked Python/Boost.Python binding sources. This probe links
# the non-gcodemodule binding objects against inert Python/runtime shims, while
# keeping src/emc/rs274ngc/gcodemodule.cc isolated because it defines Canon
# callbacks that collide with the browser runtime bridge.
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/wasm-probe-preflight-cache}
python_build_dir=${CNC_SIM_WASM_PYTHON_C_API_BUILD_DIR:-build/wasm-python-c-api-symbols}
if [[ -z "$python_build_dir" ]]; then
echo "CNC_SIM_WASM_PYTHON_C_API_BUILD_DIR must not be empty" >&2
exit 1
fi
if [[ "$python_build_dir" =~ [[:space:]] ]]; then
echo "CNC_SIM_WASM_PYTHON_C_API_BUILD_DIR must not contain whitespace: $python_build_dir" >&2
exit 1
fi
mkdir -p "$python_build_dir"
python_build_dir=$(cd "$python_build_dir" && pwd)
if [[ "$python_build_dir" == "/" ]]; then
echo "CNC_SIM_WASM_PYTHON_C_API_BUILD_DIR must not be the filesystem root" >&2
exit 1
fi
exec 9>"$python_build_dir/python-binding-link.lock"
flock 9
CNC_SIM_PREFLIGHT_CACHE_DIR="$preflight_cache_dir" \
CNC_SIM_WASM_PYTHON_C_API_BUILD_DIR="$python_build_dir" \
./test-linuxcnc-wasm-python-c-api-symbols.sh
runtime_probe=$(CNC_SIM_PREFLIGHT_CACHE_DIR="$preflight_cache_dir" ./build-linuxcnc-wasm-link-probe.sh --mode runtime "$manifest")
runtime_dir=$(dirname "$runtime_probe")
runtime_signature="$runtime_dir/link.signature"
if [[ ! -f "$runtime_signature" ]]; then
echo "missing runtime link probe signature: $runtime_signature" >&2
exit 1
fi
mapfile -t runtime_objects < <(awk 'flag{print} /^OBJECTS:$/{flag=1; next}' "$runtime_signature" | sed '/^$/d')
if [[ "${#runtime_objects[@]}" -eq 0 ]]; then
echo "runtime link probe signature did not list objects" >&2
exit 1
fi
non_gcode_binding_objects=(
"$python_build_dir/canonmodule.o"
"$python_build_dir/interpmodule.o"
"$python_build_dir/pyarrays.o"
"$python_build_dir/pyblock.o"
"$python_build_dir/pyemctypes.o"
"$python_build_dir/pyinterp1.o"
"$python_build_dir/pyparamclass.o"
)
for object in "${non_gcode_binding_objects[@]}" "$python_build_dir/gcodemodule.o"; do
if [[ ! -f "$object" ]]; then
echo "missing Python binding object: $object" >&2
exit 1
fi
done
main_source="$python_build_dir/python_binding_link_probe_main.cc"
main_object="$python_build_dir/python_binding_link_probe_main.o"
target="$python_build_dir/python_binding_link_probe"
cat > "$main_source" <<'EOF'
int main() { return 0; }
EOF
trap 'rm -f "$main_source" "$main_object"' EXIT
cxx=${CXX:-g++}
if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then
cxx="ccache $cxx"
fi
$cxx -std=c++17 -c "$main_source" -o "$main_object"
$cxx "${runtime_objects[@]}" "${non_gcode_binding_objects[@]}" "$main_object" \
-Wl,--no-gc-sections -lfmt -o "$target"
negative_log="$python_build_dir/python_binding_gcodemodule_collision.err"
if $cxx "${runtime_objects[@]}" "${non_gcode_binding_objects[@]}" "$python_build_dir/gcodemodule.o" "$main_object" \
-Wl,--no-gc-sections -lfmt -o "$python_build_dir/python_binding_link_probe_with_gcodemodule" \
2>"$negative_log"; then
echo "gcodemodule binding unexpectedly linked with runtime bridge Canon callbacks" >&2
exit 1
fi
grep -F "multiple definition of" "$negative_log" >/dev/null
grep -F "gcodemodule.o" "$negative_log" >/dev/null
grep -F "linuxcnc_canon_bridge.cpp" "$negative_log" >/dev/null
"$target"
echo "linuxcnc wasm Python binding link probe passed"

View File

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

View File

@@ -3,8 +3,12 @@ set -euo pipefail
cd "$(dirname "$0")" 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) 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" "$probe"
"$inittab_probe"
echo "linuxcnc wasm python_plugin link probe passed" echo "linuxcnc wasm python_plugin link probe passed"

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: this blocker scan inspects unresolved symbols from
# manifest-selected wasm rs274ngc_pre objects built from
# src/emc/rs274ngc/rs274ngc_pre.cc and its RS274 dependencies.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
work_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_link_blockers.XXXXXX") work_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_link_blockers.XXXXXX")
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
@@ -72,7 +75,7 @@ next_report_file="$cache_dir/rs274ngc-pre-link-blockers.txt.next"
printf 'PWD=%s\n' "$PWD" printf 'PWD=%s\n' "$PWD"
printf 'LINUXCNC_ROOT=%s\n' "$linuxcnc_root" printf 'LINUXCNC_ROOT=%s\n' "$linuxcnc_root"
printf 'MANIFEST=%s\n' "$manifest" printf 'MANIFEST=%s\n' "$manifest"
printf 'FILTER_RULE_VERSION=1\n' printf 'FILTER_RULE_VERSION=2\n'
printf 'OBJECTS:\n' printf 'OBJECTS:\n'
printf '%s\n' "${objects[@]}" printf '%s\n' "${objects[@]}"
printf 'OBJECT_STATS:\n' printf 'OBJECT_STATS:\n'
@@ -110,7 +113,7 @@ nm --defined-only "${objects[@]}" \
| awk ' | awk '
$0 ~ /^(_GLOBAL_OFFSET_TABLE_|_Unwind_Resume)$/ { next } $0 ~ /^(_GLOBAL_OFFSET_TABLE_|_Unwind_Resume)$/ { next }
$0 ~ /^(__assert_fail|__cxa_|__divdc3|__dso_handle|__errno_location|__gxx_personality_v0|__isoc23_|__muldc3|__stack_chk_fail)/ { next } $0 ~ /^(__assert_fail|__cxa_|__divdc3|__dso_handle|__errno_location|__gxx_personality_v0|__isoc23_|__muldc3|__stack_chk_fail)/ { next }
$0 ~ /^(abort|access|acos|asin|atan2|atof|atoi|basename|cabs|calloc|ceil|close|closedir|copysign|cos|cosl|exit|exp|fclose|fdatasync|fdopen|feof|fflush|fgetc|fgets|fileno|floor|fmax|fmod|fopen|fprintf|fputs|free|freelocale|fseek|fstat|ftell|fwrite|getcwd|getenv|getpid|gettimeofday|hypot|isalnum|isalpha|islower|isprint|isspace|isupper|isxdigit|link|localtime|log|lstat|malloc|memchr|memcmp|memcpy|memmove|memset|mkstemp|nearbyint|newlocale|open|opendir|perror|pow|printf|pthread_mutex_lock|pthread_mutex_unlock|putc|puts|read|readdir|realpath|realloc|remove|rename|setlocale|sin|sinl|snprintf|sprintf|sqrt|sscanf|stat|stderr|stdout|strcasecmp|strcat|strchr|strcmp|strcpy|strdup|strerror|strlen|strncasecmp|strncat|strncmp|strncpy|strrchr|strspn|strstr|strtod|strtok_r|strtol|strtoul|tan|tanl|tolower|toupper|towlower|unlink|uselocale|vfprintf|vsnprintf|wordexp|wordfree)$/ { next } $0 ~ /^(abort|access|acos|asin|atan2|atof|atoi|basename|cabs|calloc|ceil|close|closedir|copysign|cos|cosl|exit|exp|fclose|fdatasync|fdopen|feof|fflush|fgetc|fgets|fileno|floor|fmax|fmod|fopen|fprintf|fputs|free|freelocale|fseek|fstat|ftell|fwrite|getcwd|getenv|getpid|gettimeofday|hypot|isalnum|isalpha|islower|isprint|isspace|isupper|isxdigit|link|localtime|log|lstat|malloc|memchr|memcmp|memcpy|memmove|memset|mkstemp|nearbyint|newlocale|open|opendir|perror|pow|printf|pthread_mutex_lock|pthread_mutex_unlock|putc|puts|read|readdir|realpath|realloc|remove|rename|setenv|setlocale|sin|sinl|snprintf|sprintf|sqrt|sscanf|stat|stderr|stdout|strcasecmp|strcat|strchr|strcmp|strcpy|strdup|strerror|strlen|strncasecmp|strncat|strncmp|strncpy|strrchr|strspn|strstr|strtod|strtok_r|strtol|strtoul|tan|tanl|tolower|toupper|towlower|unlink|unsetenv|uselocale|vfprintf|vsnprintf|wordexp|wordfree)$/ { next }
$0 ~ /^(_ZN3fmt|_ZSt|_ZNSt|_ZNKSt|_ZNKRSt|_ZNS|_ZTI|_ZTS|_ZTV|_Zdl|_Znwm|_Znam|_Zda|_ZdaPv|_ZdaPvm|_ZdlPvm|_ZTv|_ZTh)/ { next } $0 ~ /^(_ZN3fmt|_ZSt|_ZNSt|_ZNKSt|_ZNKRSt|_ZNS|_ZTI|_ZTS|_ZTV|_Zdl|_Znwm|_Znam|_Zda|_ZdaPv|_ZdaPvm|_ZdlPvm|_ZTv|_ZTh)/ { next }
{ print } { print }
' '

View File

@@ -3,6 +3,8 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides the Interp
# init/read/execute path exercised by the source-linked probe main.
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/wasm-probe-preflight-cache} preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/wasm-probe-preflight-cache}

View File

@@ -3,6 +3,8 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc is compiled directly
# here so wasm-safe object coverage cannot drift away from the Interp source.
./build-linuxcnc-wasm-standalone-probe.sh --mode rs274ngc-pre-object >/dev/null ./build-linuxcnc-wasm-standalone-probe.sh --mode rs274ngc-pre-object >/dev/null
echo "linuxcnc wasm rs274ngc_pre object probe passed" echo "linuxcnc wasm rs274ngc_pre object probe passed"

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides read() and
# execute(), interp_convert.cc converts G0/G2/M30, and emcops.cc/dummyemcstat.cc
# provide the EMC status symbols required by this runtime link probe.
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/wasm-probe-preflight-cache} preflight_cache_dir=${CNC_SIM_PREFLIGHT_CACHE_DIR:-build/wasm-probe-preflight-cache}

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: wasm-safe object probes compile manifest-selected
# LinuxCNC RS274/tooldata sources plus narrow shims tracked in
# linuxcnc-rs274-wasm-source-files.txt.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: the wasm syntax probe compiles the
# linuxcnc-rs274-wasm-source-files.txt core entries with wasm-safe flags to
# catch browser-hostile LinuxCNC dependencies before link-time shimming.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
cxx=${CXX:-g++} cxx=${CXX:-g++}

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: this link probe runs tooldata_common through the
# standalone wasm probe compiled from src/emc/tooldata/tooldata_common.cc and
# the source-basis-checked wasm-safe shims.
probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode tooldata-common-link) probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode tooldata-common-link)
"$probe" "$probe"

View File

@@ -3,10 +3,18 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: this link probe runs the wasm-safe tooldata shim
# against LinuxCNC tooldata_mmap/tooldata_nml expectations recorded from
# src/emc/tooldata/Submakefile and tooldata runtime sources.
probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode tooldata-link) probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode tooldata-link)
"$probe" 2>"$probe.err" "$probe" 2>"$probe.err"
grep -F "tool_mmap_user(): tool mmap not available" "$probe.err" >/dev/null 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 "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
grep -F "!!!!db_init: argc exceeds MAX_DB_PROGRAM_ARGS=10" "$probe.err" >/dev/null
echo "linuxcnc wasm tooldata shim link probe passed" echo "linuxcnc wasm tooldata shim link probe passed"

View File

@@ -3,6 +3,9 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# LinuxCNC source basis: this symbol probe compares the wasm mmap backend shim
# with src/emc/tooldata/tooldata_mmap.cc exports selected by LinuxCNC
# tooldata/Submakefile.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
cxx=${CXX:-g++} cxx=${CXX:-g++}
if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then if [[ -z ${CXX:-} ]] && command -v ccache >/dev/null 2>&1; then

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-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-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-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 ./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/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 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
@@ -51,13 +64,74 @@ if ! diff -u <(printf '%s\n' "$tooldata_source_entries") <(printf '%s\n' "$toold
fi fi
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_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 '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: 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: 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
grep -F 'missing LinuxCNC switchkins REMAP INI in manifest:' check-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'missing LinuxCNC switchkins remap source in manifest:' check-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'LinuxCNC source basis: cached switchkins/remap validation hashes the LinuxCNC' check-linuxcnc-switchkins-remap-table-cached.sh >/dev/null
grep -F 'LinuxCNC source basis: generated switchkins/remap tables are extracted from' generate-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest"' generate-linuxcnc-switchkins-remap-table.sh >/dev/null
grep -F 'LinuxCNC source basis: Puma560 DH parameter output is generated from' generate-linuxcnc-puma560-dh-parameters.sh >/dev/null
grep -F 'LinuxCNC source basis: all-native guardrails lock source-backed manifests' test-all-native.sh >/dev/null
grep -F 'LinuxCNC source basis: this probe derives required Python C API exports from' test-linuxcnc-wasm-python-c-api-symbols.sh >/dev/null grep -F 'LinuxCNC source basis: this probe derives required Python C API exports from' test-linuxcnc-wasm-python-c-api-symbols.sh >/dev/null
grep -F 'LinuxCNC source basis: src/emc/rs274ngc/canonmodule.cc, interpmodule.cc,' test-linuxcnc-wasm-python-binding-link.sh >/dev/null
grep -F 'LinuxCNC wasm Python binding link probe' build-wasm.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 '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: 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
grep -F 'LinuxCNC source basis: object probes compile the core entries selected from' build-linuxcnc-wasm-probe-objects.sh >/dev/null
grep -F 'LinuxCNC source basis: wasm-safe project probe sources are the thin bridge' list-linuxcnc-wasm-safe-project-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: wasm-safe shim probe sources are limited to the' list-linuxcnc-wasm-safe-shims.sh >/dev/null
grep -F 'grep -F '\''LinuxCNC source basis: wasm-safe project probe sources are the thin bridge'\'' list-linuxcnc-wasm-safe-project-sources.sh >/dev/null' build-linuxcnc-wasm-probe-objects.sh >/dev/null
grep -F 'grep -F '\''LinuxCNC source basis: wasm-safe shim probe sources are limited to the'\'' list-linuxcnc-wasm-safe-shims.sh >/dev/null' build-linuxcnc-wasm-probe-objects.sh >/dev/null
grep -F 'rs274ngc-pre|runtime)' build-linuxcnc-wasm-probe-objects.sh >/dev/null
if grep -F 'list-cmake-set-sources.sh cnc_sim_core_sources core/' build-linuxcnc-wasm-probe-objects.sh >/dev/null; then
echo "wasm probe object builder must use the source-basis-checked project source lister" >&2
exit 1
fi
grep -F 'LinuxCNC source basis: standalone probe modes either compile LinuxCNC' build-linuxcnc-wasm-standalone-probe.sh >/dev/null
grep -F 'LinuxCNC source basis: link probe modes reuse wasm-safe objects built from' build-linuxcnc-wasm-link-probe.sh >/dev/null
grep -F 'grep -F '\''LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides'\'' "$main_source" >/dev/null' build-linuxcnc-wasm-link-probe.sh >/dev/null
grep -F 'core:src/emc/rs274ngc/rs274ngc_pre.cc:Interp implementation covered by rs274ngc_pre link probe' build-linuxcnc-wasm-standalone-probe.sh >/dev/null
grep -F 'LinuxCNC source basis: fixed-source probes resolve explicit LinuxCNC-root' list-linuxcnc-source-files.sh >/dev/null
grep -F 'LinuxCNC source path must be root-relative: $source' list-linuxcnc-source-files.sh >/dev/null
grep -F 'LinuxCNC source basis: CMake source-set listers expose only project bridge' list-cmake-set-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: combined CMake source-set listers expose project' list-cmake-set-plus-append-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: source-list filters validate project bridge/shim' list-filtered-existing-paths.sh >/dev/null
grep -F 'LinuxCNC source basis: native core smoke builds enumerate CMake-listed bridge' list-cnc-sim-core-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: these project source sets are thin bridge/adaptor' list-project-source-set.sh >/dev/null
grep -F 'LinuxCNC source basis: the RS274 dump probe pairs LinuxCNC interpreter' list-linuxcnc-rs274-dump-project-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: the bridge smoke source list is limited to the' list-linuxcnc-bridge-smoke-project-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: the RS274 API source list combines CMake-listed core' list-linuxcnc-rs274-api-project-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: manifest listers expose LinuxCNC-root relative source' list-linuxcnc-manifest-sources.sh >/dev/null
grep -F 'manifest source filter must be a manifest group name: $filter_group' list-linuxcnc-manifest-sources.sh >/dev/null
grep -F 'manifest source must be LinuxCNC-root relative: $path' list-linuxcnc-manifest-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: linuxcnc-rs274-source-files.txt enumerates the native' list-linuxcnc-source-manifest-sources.sh >/dev/null grep -F 'LinuxCNC source basis: linuxcnc-rs274-source-files.txt enumerates the native' list-linuxcnc-source-manifest-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: linuxcnc-kinematics-source-files.txt tracks' list-linuxcnc-kinematics-manifest-sources.sh >/dev/null
grep -F 'LinuxCNC source basis: native source-link support objects come from the' list-linuxcnc-source-support-objects.sh >/dev/null grep -F 'LinuxCNC source basis: native source-link support objects come from the' list-linuxcnc-source-support-objects.sh >/dev/null
grep -F 'LinuxCNC source basis: native RS274/source-link builds consume headers from' list-linuxcnc-source-common-cxxflags.sh >/dev/null
grep -F 'LinuxCNC source basis: wasm-safe probes compile the manifest-selected' list-linuxcnc-wasm-common-cxxflags.sh >/dev/null
grep -F 'LinuxCNC source basis: native source-link probes link against the built' list-linuxcnc-native-linkflags.sh >/dev/null
grep -F 'LinuxCNC source basis: this probe compiles the core RS274 interpreter sources' test-linuxcnc-source-objects.sh >/dev/null grep -F 'LinuxCNC source basis: this probe compiles the core RS274 interpreter sources' test-linuxcnc-source-objects.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: this syntax probe checks the native RS274 manifest' test-linuxcnc-source-syntax.sh >/dev/null
grep -F 'LinuxCNC source basis: native bridge object caches compile only project' build-linuxcnc-native-bridge-objects.sh >/dev/null
grep -F 'LinuxCNC source basis: the native bridge smoke links project' test-linuxcnc-bridge-native.sh >/dev/null
grep -F 'LinuxCNC source basis: the native API smoke links the thin cnc_sim API bridge' test-linuxcnc-api-native.sh >/dev/null
grep -F 'LinuxCNC source basis: the native RS274 smoke exercises the LinuxCNC-backed' test-linuxcnc-rs274-native.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 'LinuxCNC source basis: wasm-safe object probes compile manifest-selected' test-linuxcnc-wasm-source-objects.sh >/dev/null
grep -F 'LinuxCNC source basis: the wasm syntax probe compiles the' test-linuxcnc-wasm-source-syntax.sh >/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 blocker scan inspects unresolved symbols from' test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >/dev/null
grep -F 'header:src/emc/ini/inifile.h:LinuxCNC C INI reader declarations exported by ini Submakefile' linuxcnc-rs274-wasm-source-files.txt >/dev/null grep -F 'header:src/emc/ini/inifile.h:LinuxCNC C INI reader declarations exported by ini Submakefile' linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'header:src/emc/ini/inifile.hh:LinuxCNC C++ INI reader declarations used by inifile.cc' linuxcnc-rs274-wasm-source-files.txt >/dev/null grep -F 'header:src/emc/ini/inifile.hh:LinuxCNC C++ INI reader declarations used by inifile.cc' linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'metadata:src/emc/ini/Submakefile:LinuxCNC ini build metadata defining liblinuxcncini source and exported headers' linuxcnc-rs274-wasm-source-files.txt >/dev/null grep -F 'metadata:src/emc/ini/Submakefile:LinuxCNC ini build metadata defining liblinuxcncini source and exported headers' linuxcnc-rs274-wasm-source-files.txt >/dev/null
@@ -79,6 +153,31 @@ grep -F 'metadata:src/emc/task/taskclass.cc:source basis for wasm-safe builtin P
grep -F 'metadata:src/emc/task/emccanon.cc:source basis for wasm-safe tooldata API declaration shim' linuxcnc-rs274-wasm-source-files.txt >/dev/null grep -F 'metadata:src/emc/task/emccanon.cc:source basis for wasm-safe tooldata API declaration shim' linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'metadata:src/emc/sai/dummyemcstat.cc:source basis for wasm-safe emcStatus singleton shim' linuxcnc-rs274-wasm-source-files.txt >/dev/null grep -F 'metadata:src/emc/sai/dummyemcstat.cc:source basis for wasm-safe emcStatus singleton shim' linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'blocked:src/emc/rs274ngc/gcodemodule.cc:Python gcode module binding, tracked for full rs274ngc source coverage' linuxcnc-rs274-wasm-source-files.txt >/dev/null grep -F 'blocked:src/emc/rs274ngc/gcodemodule.cc:Python gcode module binding, tracked for full rs274ngc source coverage' linuxcnc-rs274-wasm-source-files.txt >/dev/null
grep -F 'expected LinuxCNC parameter env override not to leak into default parses' core/tests/cnc_sim_api_smoke.cpp >/dev/null
grep -F 'SET_PARAMETER_FILE_NAME("rs274ngc.var")' core/src/linuxcnc_rs274_backend.cpp >/dev/null
grep -F 'expected LinuxCNC INI PARAMETER_FILE to restore numbered parameters' core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp >/dev/null
grep -F 'expected LinuxCNC config INI_FILE_NAME to back #<_ini[]> named parameters' core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp >/dev/null
grep -F 'interp->ini_load(options.ini_file_name.c_str())' core/src/linuxcnc_rs274_backend.cpp >/dev/null
grep -F 'ScopedEnvironmentVariable ini_file_env("INI_FILE_NAME", options.ini_file_name)' core/src/linuxcnc_rs274_backend.cpp >/dev/null
grep -F 'convert_lathe_diameter_mode() scales G7/G8 X words and G76 I/J/K words' test-linuxcnc-rs274-native.sh >/dev/null
grep -F 'convert_lathe_diameter_mode() scales G7/G8 X words and G76 I/J/K words' test-linuxcnc-source-link.sh >/dev/null
grep -F 'tests/gcode/linuxcnc_lathe_diameter_mode.ngc' docs/linuxcnc-porting.md >/dev/null
grep -F 'tests/gcode/linuxcnc_lathe_diameter_g76.ngc' docs/linuxcnc-porting.md >/dev/null
grep -F 'missing source-linked G7 diameter-mode G76 final threading pass' test-linuxcnc-source-link.sh >/dev/null
grep -F 'convert_tool_length_offset() rejects invalid G43.2 H/axis combinations' test-linuxcnc-source-link.sh >/dev/null
grep -F 'linuxcnc_g43_2_missing_h_axis_error|G43.2: No axes specified and H word missing' test-linuxcnc-source-link.sh >/dev/null
grep -F 'convert_tool_select() and convert_m() M61 both route through' test-linuxcnc-source-link.sh >/dev/null
grep -F 'read_atsign()' test-linuxcnc-source-link.sh >/dev/null
grep -F 'tests/gcode/linuxcnc_polar_coordinates.ngc' test-linuxcnc-source-link.sh >/dev/null
grep -F 'tests/gcode/linuxcnc_polar_coordinate_variants.ngc' test-linuxcnc-source-link.sh >/dev/null
grep -F 'enhance_block() assigns motion_to_be' test-linuxcnc-source-link.sh >/dev/null
grep -F 'linuxcnc_g80_axis_error|Cannot use axis values with g80' test-linuxcnc-source-link.sh >/dev/null
grep -F 'check_g_codes() rejects invalid modal-0 combinations' test-linuxcnc-source-link.sh >/dev/null
grep -F 'linuxcnc_g53_incremental_error|Cannot use g53 incremental' test-linuxcnc-source-link.sh >/dev/null
grep -F 'lookup_named_param()' test-linuxcnc-rs274-native.sh >/dev/null
grep -F 'tests/gcode/linuxcnc_readonly_named_parameters.ngc' docs/linuxcnc-porting.md >/dev/null
grep -F 'rs274ngc_pre.cc ini_load() consumes INI_FILE_NAME' test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'rs274ngc_pre.cc ini_load() consumes INI_FILE_NAME' web/test-browser-wasm-smoke-linuxcnc-sections.js >/dev/null
grep -F 'metadata:src/emc/nml_intf/emcops.cc:source basis for native source-link EMC status constructor support object' linuxcnc-rs274-source-files.txt >/dev/null grep -F 'metadata:src/emc/nml_intf/emcops.cc:source basis for native source-link EMC status constructor support object' linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'metadata:src/emc/sai/dummyemcstat.cc:source basis for native source-link emcStatus singleton support object' linuxcnc-rs274-source-files.txt >/dev/null grep -F 'metadata:src/emc/sai/dummyemcstat.cc:source basis for native source-link emcStatus singleton support object' linuxcnc-rs274-source-files.txt >/dev/null
grep -F 'metadata:src/emc/sai/Submakefile:LinuxCNC SAI build metadata defining dummyemcstat support object' linuxcnc-rs274-source-files.txt >/dev/null grep -F 'metadata:src/emc/sai/Submakefile:LinuxCNC SAI build metadata defining dummyemcstat support object' linuxcnc-rs274-source-files.txt >/dev/null
@@ -126,7 +225,21 @@ grep -F 'fiveaxisKinematicsType: options.fiveaxisKinematicsType' web/src/wasm-co
grep -F 'fiveaxis_kinematics_type: options.fiveaxis_kinematics_type' web/src/wasm-core.js >/dev/null grep -F 'fiveaxis_kinematics_type: options.fiveaxis_kinematics_type' web/src/wasm-core.js >/dev/null
grep -F 'xyzbcTrt: options.xyzbcTrt' web/src/wasm-core.js >/dev/null grep -F 'xyzbcTrt: options.xyzbcTrt' web/src/wasm-core.js >/dev/null
grep -F 'globalThis.createCncSimModule' web/src/wasm-core.js >/dev/null grep -F 'globalThis.createCncSimModule' web/src/wasm-core.js >/dev/null
grep -F 'createModule(moduleOptions)' web/src/wasm-core.js >/dev/null grep -F 'const hasOpfsOptions = Object.prototype.hasOwnProperty.call(moduleOptions, "opfs");' web/src/wasm-core.js >/dev/null
grep -F 'const emscriptenModuleOptions = hasOpfsOptions' web/src/wasm-core.js >/dev/null
grep -F '? (({ opfs: _opfs, ...rest }) => rest)(moduleOptions)' web/src/wasm-core.js >/dev/null
grep -F 'const module = hasOpfsOptions ? await createModule(emscriptenModuleOptions) : await createModule(moduleOptions);' web/src/wasm-core.js >/dev/null
grep -F 'const opfs = installOpfsWorkspace(module, moduleOptions);' web/src/wasm-core.js >/dev/null
if ! awk '
index($0, "const hasOpfsOptions = Object.prototype.hasOwnProperty.call(moduleOptions, \"opfs\");") { has_line = NR }
index($0, "const emscriptenModuleOptions = hasOpfsOptions") { stripped_line = NR }
index($0, "await createModule(emscriptenModuleOptions)") { create_line = NR }
index($0, "const opfs = installOpfsWorkspace(module, moduleOptions);") { opfs_line = NR }
END { exit !(has_line && stripped_line && create_line && opfs_line && has_line < stripped_line && stripped_line < create_line && create_line < opfs_line) }
' web/src/wasm-core.js; then
echo "wasm-core must strip OPFS options before creating the Emscripten module and install OPFS from the original options" >&2
exit 1
fi
grep -F 'async parseFile(path, dialect = "linuxcnc", options = {})' web/src/wasm-core.js >/dev/null grep -F 'async parseFile(path, dialect = "linuxcnc", options = {})' web/src/wasm-core.js >/dev/null
grep -F 'async parseWithParameterFile(program, parameterPath, dialect = "linuxcnc", options = {})' web/src/wasm-core.js >/dev/null grep -F 'async parseWithParameterFile(program, parameterPath, dialect = "linuxcnc", options = {})' 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 'async parseFileWithParameterFile(path, parameterPath, dialect = "linuxcnc", options = {})' web/src/wasm-core.js >/dev/null
@@ -184,6 +297,8 @@ grep -F 'async function statOpfsEntry(workspacePath, relativePath)' web/src/wasm
grep -F 'async function readOpfsDirectoryTree(workspacePath, relativePath, relativeRoot = "")' web/src/wasm-core.js >/dev/null grep -F 'async function readOpfsDirectoryTree(workspacePath, relativePath, relativeRoot = "")' web/src/wasm-core.js >/dev/null
grep -F 'function readWasmDirectoryTree(module, rootPath, relativeRoot = "")' web/src/wasm-core.js >/dev/null grep -F 'function readWasmDirectoryTree(module, rootPath, relativeRoot = "")' web/src/wasm-core.js >/dev/null
grep -F 'function writeWasmFileReplacingPath(module, path, data)' web/src/wasm-core.js >/dev/null grep -F 'function writeWasmFileReplacingPath(module, path, data)' web/src/wasm-core.js >/dev/null
grep -F 'path.includes("\\")' web/src/wasm-core.js >/dev/null
grep -F 'path.includes("\0")' web/src/wasm-core.js >/dev/null
grep -F '`${LINUXCNC_DEFAULT_PARAMETER_FILE}.bak`' web/src/wasm-core.js >/dev/null grep -F '`${LINUXCNC_DEFAULT_PARAMETER_FILE}.bak`' web/src/wasm-core.js >/dev/null
grep -F 'export type WasmModuleOptions' web/src/wasm-core.d.ts >/dev/null grep -F 'export type WasmModuleOptions' web/src/wasm-core.d.ts >/dev/null
grep -F 'printErr?: (text: string) => void;' web/src/wasm-core.d.ts >/dev/null grep -F 'printErr?: (text: string) => void;' web/src/wasm-core.d.ts >/dev/null
@@ -216,6 +331,10 @@ grep -F '"field":"subroutinePath"' web/public/linuxcnc_switchkins_remap_config_c
grep -F '"field":"subroutinepath"' web/public/linuxcnc_switchkins_remap_config_cases.json >/dev/null grep -F '"field":"subroutinepath"' web/public/linuxcnc_switchkins_remap_config_cases.json >/dev/null
grep -F '"field":"subroutine_path"' web/public/linuxcnc_switchkins_remap_config_cases.json >/dev/null grep -F '"field":"subroutine_path"' web/public/linuxcnc_switchkins_remap_config_cases.json >/dev/null
grep -F '"field":"SUBROUTINE_PATH"' web/public/linuxcnc_switchkins_remap_config_cases.json >/dev/null grep -F '"field":"SUBROUTINE_PATH"' web/public/linuxcnc_switchkins_remap_config_cases.json >/dev/null
if grep -F '"field":"tool' web/public/linuxcnc_switchkins_remap_config_cases.json; then
echo "LinuxCNC TOOL_TABLE must remain source coverage only, not generated browser switchkins cases" >&2
exit 1
fi
grep -F '{"switchkins", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null grep -F '{"switchkins", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null
grep -F '{"remap", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null grep -F '{"remap", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null
grep -F '{"machine", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null grep -F '{"machine", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null
@@ -245,12 +364,17 @@ grep -F '{"subroutinePath", ' core/tests/linuxcnc_switchkins_remap_config_cases.
grep -F '{"subroutinepath", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null grep -F '{"subroutinepath", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null
grep -F '{"subroutine_path", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null grep -F '{"subroutine_path", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null
grep -F '{"SUBROUTINE_PATH", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null grep -F '{"SUBROUTINE_PATH", ' core/tests/linuxcnc_switchkins_remap_config_cases.inc >/dev/null
if grep -F '{"tool' core/tests/linuxcnc_switchkins_remap_config_cases.inc; then
echo "LinuxCNC TOOL_TABLE must remain source coverage only, not generated C++ switchkins cases" >&2
exit 1
fi
grep -F '"moduleResolution": "Bundler"' web/tsconfig.json >/dev/null grep -F '"moduleResolution": "Bundler"' web/tsconfig.json >/dev/null
grep -F '"include": ["src/index.ts", "src/**/*.d.ts"]' web/tsconfig.json >/dev/null grep -F '"include": ["src/index.ts", "src/**/*.d.ts"]' web/tsconfig.json >/dev/null
grep -F 'createWasmSimulator' test-web-wasm-node-smoke.cjs >/dev/null grep -F 'createWasmSimulator' test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'locateFile: (file) => path.join(wasmDir, file)' test-web-wasm-node-smoke.cjs >/dev/null grep -F 'locateFile: (file) => path.join(wasmDir, file)' test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'printErr: () => {}' test-web-wasm-node-smoke.cjs >/dev/null grep -F 'printErr: () => {}' test-web-wasm-node-smoke.cjs >/dev/null
grep -F 'expected web wasm-core options to pass LinuxCNC xyzac-trt RTCP config into WASM' test-web-wasm-node-smoke.cjs >/dev/null grep -F 'expected web wasm-core options to pass LinuxCNC xyzac-trt RTCP config into WASM' test-web-wasm-node-smoke.cjs >/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 'node test-web-wasm-node-smoke.cjs' test-web-wasm-node-smoke.sh >/dev/null grep -F 'node test-web-wasm-node-smoke.cjs' test-web-wasm-node-smoke.sh >/dev/null
grep -F 'WASM_NODE_SMOKE_MIN_STEPS:-50' test-web-wasm-node-smoke.sh >/dev/null grep -F 'WASM_NODE_SMOKE_MIN_STEPS:-50' test-web-wasm-node-smoke.sh >/dev/null
grep -F 'web wasm node smoke passed ([0-9][0-9]* steps)' test-web-wasm-node-smoke.sh >/dev/null grep -F 'web wasm node smoke passed ([0-9][0-9]* steps)' test-web-wasm-node-smoke.sh >/dev/null
@@ -265,6 +389,8 @@ grep -F 'runBrowserOpfsParameterSections' web/test-browser-wasm-smoke-opfs-works
grep -F 'runBrowserOpfsMirrorSections' web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null grep -F 'runBrowserOpfsMirrorSections' web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'runBrowserOpfsDirectorySections' web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null grep -F 'runBrowserOpfsDirectorySections' web/test-browser-wasm-smoke-opfs-workspace-sections.js >/dev/null
grep -F 'await simulator.parseFile("programs/browser-smoke.ngc", "linuxcnc"' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null grep -F 'await simulator.parseFile("programs/browser-smoke.ngc", "linuxcnc"' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'parseFileWithParameterFileProgram' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'parseFileWithParameterFileParameter' web/test-browser-wasm-smoke-opfs-basic-sections.js >/dev/null
grep -F 'await simulator.parseWithParameterFile(' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null grep -F 'await simulator.parseWithParameterFile(' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'await simulator.parseFileWithParameterFile(' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null grep -F 'await simulator.parseFileWithParameterFile(' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'OPFS copyFile did not create the backup file' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null grep -F 'OPFS copyFile did not create the backup file' web/test-browser-wasm-smoke-opfs-mirror-sections.js >/dev/null
@@ -279,13 +405,16 @@ grep -F '"parameters/rs274ngc.var"' web/test-browser-wasm-smoke-opfs-parameter-s
grep -F '"parameters/rs274ngc.var.bak"' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null grep -F '"parameters/rs274ngc.var.bak"' web/test-browser-wasm-smoke-opfs-parameter-sections.js >/dev/null
grep -F 'const opfsOptions = options.opfs ?? (isOpfsAvailable() ? {} : false)' web/src/wasm-core.js >/dev/null grep -F 'const opfsOptions = options.opfs ?? (isOpfsAvailable() ? {} : false)' web/src/wasm-core.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 cannot be disabled in browser contexts with OPFS support' web/src/wasm-core.js >/dev/null
if grep -R -n -E 'localStorage|sessionStorage|indexedDB|showOpenFilePicker|showSaveFilePicker|webkitRequestFileSystem|FileReader' web/src; then 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 echo "browser app/wasm code must not add filesystem persistence outside OPFS" >&2
exit 1 exit 1
fi fi
grep -F 'LinuxCNC source basis: rs274ngc_pre.cc restore_parameters() reads the' 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 'before replacing the main file and managing filename + ".bak".' web/src/wasm-core.js >/dev/null
grep -F 'await removeOpfsEntry(workspacePath, `${path}.new`, false);' web/src/wasm-core.js >/dev/null grep -F 'async function removeOpfsFileEntry(workspacePath, relativePath)' web/src/wasm-core.js >/dev/null
grep -F 'await removeOpfsFileEntry(workspacePath, `${path}.new`);' web/src/wasm-core.js >/dev/null
grep -F 'removeWasmFilePath(module, `${LINUXCNC_DEFAULT_PARAMETER_FILE}.new`);' web/src/wasm-core.js >/dev/null
grep -F 'OPFS removeFile removed a directory instead of preserving file-only semantics' web/test-browser-wasm-smoke-opfs-directory-sections.js >/dev/null
grep -F 'OPFS workspace is required for browser program parsing' web/src/app.js >/dev/null grep -F 'OPFS workspace is required for browser program parsing' 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 'const events = await simulator.parseFileWithParameterFile(programPath, parameterPath, "linuxcnc", parseOptions);' web/src/app.js >/dev/null
grep -F 'default browser simulator did not create an OPFS workspace' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null grep -F 'default browser simulator did not create an OPFS workspace' web/test-browser-wasm-smoke-opfs-policy-sections.js >/dev/null
@@ -302,6 +431,7 @@ grep -F 'LinuxCNC out-of-range parameter file remained in WASM FS after restore
grep -F 'browser wasm smoke passed' web/test-browser-wasm-smoke-helpers.js >/dev/null grep -F 'browser wasm smoke passed' web/test-browser-wasm-smoke-helpers.js >/dev/null
grep -F 'browser wasm smoke passed' web/test-browser-wasm-smoke.html >/dev/null grep -F 'browser wasm smoke passed' web/test-browser-wasm-smoke.html >/dev/null
grep -F 'chromium chromium-browser google-chrome' test-web-wasm-browser-smoke.sh >/dev/null grep -F 'chromium chromium-browser google-chrome' test-web-wasm-browser-smoke.sh >/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 'browser wasm smoke passed' test-web-wasm-browser-smoke.sh >/dev/null grep -F 'browser wasm smoke passed' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'BROWSER_SMOKE_MIN_SECTIONS:-50' test-web-wasm-browser-smoke.sh >/dev/null grep -F 'BROWSER_SMOKE_MIN_SECTIONS:-50' test-web-wasm-browser-smoke.sh >/dev/null
grep -F 'browser wasm smoke passed ([0-9][0-9]* sections)' test-web-wasm-browser-smoke.sh >/dev/null grep -F 'browser wasm smoke passed ([0-9][0-9]* sections)' test-web-wasm-browser-smoke.sh >/dev/null
@@ -567,12 +697,30 @@ fi
grep -Fx "src/emc/kinematics/switchkins.h" "$kinematics_manifest_sources" >/dev/null grep -Fx "src/emc/kinematics/switchkins.h" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/emc/kinematics/Submakefile" "$kinematics_manifest_sources" >/dev/null grep -Fx "src/emc/kinematics/Submakefile" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/emc/kinematics/blend.fig" "$kinematics_manifest_sources" >/dev/null grep -Fx "src/emc/kinematics/blend.fig" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/objects/man/man9/corexy_by_hal.9.adoc" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/objects/man/man9/gantry.9.adoc" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/objects/man/man9/matrixkins.9.adoc" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/objects/man/man9/millturn.9.adoc" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/objects/man/man9/userkins.9.adoc" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/objects/man/man9/xyzab_tdr_kins.9.adoc" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/objects/man/man9/xyzacb_trsrn.9.adoc" "$kinematics_manifest_sources" >/dev/null
grep -Fx "src/objects/man/man9/xyzbca_trsrn.9.adoc" "$kinematics_manifest_sources" >/dev/null
if [[ "$(grep -Ec '^src/objects/man/man9/(corexy_by_hal|gantry|matrixkins|millturn|userkins|xyzab_tdr_kins|xyzacb_trsrn|xyzbca_trsrn)\.9\.adoc$' "$kinematics_manifest_sources")" -ne 8 ]]; then
echo "expected kinematics manifest to cover all LinuxCNC generated component manpage sources" >&2
exit 1
fi
grep -F 'manifest does not group all LinuxCNC kinematics headers as header sources' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not group all LinuxCNC kinematics headers as header sources' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not group all LinuxCNC kinematics build metadata as metadata sources' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not group all LinuxCNC kinematics build metadata as metadata sources' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not group all LinuxCNC kinematics reference assets as asset sources' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not group all LinuxCNC kinematics reference assets as asset sources' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not cover all LinuxCNC component sources referenced by M428/M429/M430 INI KINEMATICS entries' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not cover all LinuxCNC component sources referenced by M428/M429/M430 INI KINEMATICS entries' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not cover all LinuxCNC generated component sources referenced by M428/M429/M430 INI KINEMATICS entries' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not cover all LinuxCNC generated component sources referenced by M428/M429/M430 INI KINEMATICS entries' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not cover all LinuxCNC generated component make metadata adjacent to generated kinematics sources' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not cover all LinuxCNC generated component make metadata adjacent to generated kinematics sources' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not cover all LinuxCNC generated component manpage sources adjacent to generated kinematics sources' check-linuxcnc-inputs.sh >/dev/null
grep -F 'configs/sim/axis/vismach/hexapod-sim/hexapod.ini TOOL_TABLE=hexapod.tbl' check-linuxcnc-inputs.sh >/dev/null
grep -F 'configs/sim/axis/vismach/scara/scara.ini TOOL_TABLE=scara.tbl' check-linuxcnc-inputs.sh >/dev/null
grep -F 'configs/sim/qtvcp_screens/non-trivial/scara/scara.ini TOOL_TABLE=scara.tbl' check-linuxcnc-inputs.sh >/dev/null
grep -F 'LinuxCNC M428/M429/M430 INI TOOL_TABLE files have new unresolved sources' check-linuxcnc-inputs.sh >/dev/null
grep -F 'LinuxCNC M428/M429/M430 INI TOOL_TABLE unresolved source set changed' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not cover all resolvable LinuxCNC TOOL_TABLE files referenced by M428/M429/M430 INI files as tooldata sources' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not cover all resolvable LinuxCNC TOOL_TABLE files referenced by M428/M429/M430 INI files as tooldata sources' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not cover LinuxCNC README sources adjacent to M428/M429/M430 INI files as asset sources' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not cover LinuxCNC README sources adjacent to M428/M429/M430 INI files as asset sources' check-linuxcnc-inputs.sh >/dev/null
grep -F 'manifest does not cover all LinuxCNC vismach user component sources referenced by M428/M429/M430 INI/HAL loadusr commands as asset sources' check-linuxcnc-inputs.sh >/dev/null grep -F 'manifest does not cover all LinuxCNC vismach user component sources referenced by M428/M429/M430 INI/HAL loadusr commands as asset sources' check-linuxcnc-inputs.sh >/dev/null

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