diff --git a/check-linuxcnc-switchkins-remap-table.sh b/check-linuxcnc-switchkins-remap-table.sh index 77780df..6c8833b 100755 --- a/check-linuxcnc-switchkins-remap-table.sh +++ b/check-linuxcnc-switchkins-remap-table.sh @@ -363,6 +363,14 @@ validate_generator_cli_contract() { local cli_manifest_with_unused_postgui local cli_manifest_with_unused_remap local cli_manifest_with_unused_tooldata + 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_hal local cli_manifest_without_postgui @@ -380,6 +388,14 @@ validate_generator_cli_contract() { 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_tooldata="$cli_dir/bridgemill-manifest-with-unused-tooldata.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_hal="$cli_dir/bridgemill-manifest-without-hal.txt" cli_manifest_without_postgui="$cli_dir/bridgemill-manifest-without-postgui.txt" @@ -549,6 +565,118 @@ validate_generator_cli_contract() { 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 + 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" \ >"$cli_dir/linuxcnc_switchkins_remap_table.stdout.inc" diff --git a/generate-linuxcnc-switchkins-remap-table.sh b/generate-linuxcnc-switchkins-remap-table.sh index 4c5984d..892a89b 100755 --- a/generate-linuxcnc-switchkins-remap-table.sh +++ b/generate-linuxcnc-switchkins-remap-table.sh @@ -100,10 +100,26 @@ read_ini_switchkins_config() { print "halfile\t" value } else if (section == "hal" && key == "postgui_halfile") { print "postgui_halfile\t" value + } else if (section == "hal" && key == "halcmd") { + lower = tolower(value) + if (lower ~ /(^|[[:space:]])loadusr([[:space:]]|$)/ && + match(lower, /[^[:space:]]+\.py([[:space:]]|$)/)) { + py = substr(value, RSTART, RLENGTH) + sub(/[[:space:]]*$/, "", py) + print "halcmd_loadusr_py\t" py + } } else if (section == "emcio" && key == "tool_table") { print "tool_table\t" value } else if (section == "rs274ngc" && key == "subroutine_path") { 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:]]*[[:space:]]*[Cc][Aa][Ll][Ll].*$/, "", ngc) + print "on_abort_ngc\t" ngc + } } else if (section == "rs274ngc" && key == "remap") { lower = tolower(value) for (mcode = 428; mcode <= 430; mcode++) { @@ -114,6 +130,20 @@ read_ini_switchkins_config() { 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" @@ -156,9 +186,12 @@ declare -A manifest_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_remap_count=0 manifest_tooldata_count=0 +manifest_asset_count=0 validate_manifest_source_path() { local group=$1 @@ -226,6 +259,15 @@ while IFS=: read -r manifest_group manifest_path _manifest_note; do 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 done <"$manifest" @@ -322,6 +364,108 @@ tooldata_source_for_tool_table() { 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_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= declare -A kinstype_cache=() kinstype_for_remap_source() { @@ -664,6 +808,12 @@ while IFS=: read -r group path _note; do postgui_halfiles= tool_tables= subroutine_paths= + remap_ngcs= + on_abort_ngcs= + python_paths= + python_toplevels= + python_remaps= + halcmd_loadusr_pys= remap_ngc_428= remap_ngc_429= remap_ngc_430= @@ -687,6 +837,24 @@ while IFS=: read -r group path _note; do subroutine_path) 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_py) + halcmd_loadusr_pys+="${halcmd_loadusr_pys:+$'\n'}$ini_value" + ;; remap_428) [[ -n "$remap_ngc_428" ]] || remap_ngc_428=$ini_value ;; @@ -799,6 +967,44 @@ while IFS=: read -r group path _note; do 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_py; do + [[ -n "$loadusr_py" ]] || continue + if asset_source_for_path "$config" "$loadusr_py"; then + halcmd_loadusr_source=$resolved_asset_source + record_asset_source "$path" "$halcmd_loadusr_source" "HALCMD loadusr" + record_python_import_assets "$path" "$halcmd_loadusr_source" + fi + done <<<"$halcmd_loadusr_pys" for remap_source in "$remap_source_428" "$remap_source_429" "$remap_source_430"; do if [[ -n "$remap_source" ]]; then @@ -821,6 +1027,7 @@ done <"$manifest" unique_config_source_count=${#used_config_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() { if ((manifest_config_count == 0)); then @@ -871,6 +1078,10 @@ validate_generated_content() { 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 echo "LinuxCNC remap manifest coverage mismatch: resolved $unique_resolved_remap_count of $manifest_remap_count remap sources" >&2 local remap_source