#!/usr/bin/env bash set -euo pipefail 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} manifest=${1:-} require_complete=${2:-} allowed_manifest_groups=( asset binding blocked blocked-header component config core generated header metadata remap tooldata ) contains_manifest_group() { local candidate=$1 local group for group in "${allowed_manifest_groups[@]}"; do [[ "$group" == "$candidate" ]] && return 0 done return 1 } if [[ "$manifest" == "--root-only" ]]; then manifest= fi if [[ "$manifest" == "--require-rs274-complete" || "$manifest" == "--require-kinematics-complete" ]]; then echo "usage: $0 [manifest] [--require-rs274-complete|--require-kinematics-complete]" >&2 exit 1 fi case "$require_complete" in ""|--require-rs274-complete|--require-kinematics-complete) ;; *) echo "unknown check-linuxcnc-inputs option: $require_complete" >&2 exit 1 ;; esac if [[ -n "$manifest" && ! -f "$manifest" ]]; then echo "missing manifest: $manifest" >&2 exit 1 fi if [[ ! -d "$linuxcnc_root" ]]; then echo "missing LinuxCNC root: $linuxcnc_root" >&2 exit 1 fi linuxcnc_root=$(cd "$linuxcnc_root" && pwd) if [[ -n "$manifest" ]]; then seen_manifest_sources=() manifest_line_number=0 while IFS= read -r manifest_line || [[ -n "$manifest_line" ]]; do manifest_line_number=$((manifest_line_number + 1)) case "$manifest_line" in ""|\#*) 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 if ! contains_manifest_group "$group"; then echo "unknown manifest group: $group" >&2 exit 1 fi if [[ "$path" = /* || "$path" == .. || "$path" == ../* || "$path" == */.. || "$path" == */../* ]]; then echo "manifest source must be LinuxCNC-root relative: $path" >&2 exit 1 fi if printf '%s\n' "${seen_manifest_sources[@]}" | grep -Fx -- "$path" >/dev/null; then echo "duplicate manifest source: $path" >&2 exit 1 fi if [[ ! -f "$linuxcnc_root/$path" ]]; then echo "missing manifest source: $path" >&2 exit 1 fi seen_manifest_sources+=("$path") done < "$manifest" fi if [[ -n "$manifest" && "$require_complete" == "--require-rs274-complete" ]]; then rs274_dir="$linuxcnc_root/src/emc/rs274ngc" if [[ ! -d "$rs274_dir" ]]; then echo "missing LinuxCNC rs274ngc source directory: $rs274_dir" >&2 exit 1 fi mapfile -t rs274_sources < <( find "$rs274_dir" -maxdepth 1 -type f \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_rs274_sources < <( awk -F: ' $1 !~ /^($|#)/ && $2 ~ /^src\/emc\/rs274ngc\// { print $2 } ' "$manifest" | sort -u ) missing_rs274_sources=() for source in "${rs274_sources[@]}"; do if ! printf '%s\n' "${manifest_rs274_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_rs274_sources+=("$source") fi done if ((${#missing_rs274_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC rs274ngc sources: $manifest" >&2 printf 'missing rs274ngc source: %s\n' "${missing_rs274_sources[@]}" >&2 exit 1 fi fi if [[ -n "$manifest" && "$require_complete" == "--require-kinematics-complete" ]]; then kinematics_dir="$linuxcnc_root/src/emc/kinematics" if [[ ! -d "$kinematics_dir" ]]; then echo "missing LinuxCNC kinematics source directory: $kinematics_dir" >&2 exit 1 fi mapfile -t kinematics_sources < <( find "$kinematics_dir" -maxdepth 1 -type f \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_kinematics_sources < <( awk -F: ' $1 !~ /^($|#)/ && $2 ~ /^src\/emc\/kinematics\// { print $2 } ' "$manifest" | sort -u ) missing_kinematics_sources=() for source in "${kinematics_sources[@]}"; do if ! printf '%s\n' "${manifest_kinematics_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_kinematics_sources+=("$source") fi done if ((${#missing_kinematics_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC kinematics sources: $manifest" >&2 printf 'missing kinematics source: %s\n' "${missing_kinematics_sources[@]}" >&2 exit 1 fi mapfile -t kinematics_header_sources < <( find "$kinematics_dir" -maxdepth 1 -type f -name '*.h' \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_kinematics_header_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "header" && $2 ~ /^src\/emc\/kinematics\/.*\.h$/ { print $2 } ' "$manifest" | sort -u ) mapfile -t kinematics_metadata_sources < <( find "$kinematics_dir" -maxdepth 1 -type f \ \( -name 'Submakefile' -o -name 'meson.build' \) \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_kinematics_metadata_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "metadata" && $2 ~ /^src\/emc\/kinematics\/(Submakefile|meson\.build)$/ { print $2 } ' "$manifest" | sort -u ) mapfile -t kinematics_asset_sources < <( find "$kinematics_dir" -maxdepth 1 -type f -name '*.fig' \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_kinematics_asset_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "asset" && $2 ~ /^src\/emc\/kinematics\/.*\.fig$/ { print $2 } ' "$manifest" | sort -u ) missing_kinematics_header_sources=() for source in "${kinematics_header_sources[@]}"; do if ! printf '%s\n' "${manifest_kinematics_header_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_kinematics_header_sources+=("$source") fi done if ((${#missing_kinematics_header_sources[@]} > 0)); then echo "manifest does not group all LinuxCNC kinematics headers as header sources: $manifest" >&2 printf 'missing kinematics header source: %s\n' "${missing_kinematics_header_sources[@]}" >&2 exit 1 fi missing_kinematics_metadata_sources=() for source in "${kinematics_metadata_sources[@]}"; do if ! printf '%s\n' "${manifest_kinematics_metadata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_kinematics_metadata_sources+=("$source") fi done if ((${#missing_kinematics_metadata_sources[@]} > 0)); then echo "manifest does not group all LinuxCNC kinematics build metadata as metadata sources: $manifest" >&2 printf 'missing kinematics metadata source: %s\n' "${missing_kinematics_metadata_sources[@]}" >&2 exit 1 fi missing_kinematics_asset_sources=() for source in "${kinematics_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_kinematics_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_kinematics_asset_sources+=("$source") fi done if ((${#missing_kinematics_asset_sources[@]} > 0)); then echo "manifest does not group all LinuxCNC kinematics reference assets as asset sources: $manifest" >&2 printf 'missing kinematics asset source: %s\n' "${missing_kinematics_asset_sources[@]}" >&2 exit 1 fi posemath_dir="$linuxcnc_root/src/libnml/posemath" if [[ ! -d "$posemath_dir" ]]; then echo "missing LinuxCNC posemath source directory: $posemath_dir" >&2 exit 1 fi mapfile -t posemath_core_sources < <( find "$posemath_dir" -maxdepth 1 -type f \ \( -name '*.c' -o -name '*.cc' \) \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_posemath_core_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "core" && $2 ~ /^src\/libnml\/posemath\/.*\.(c|cc)$/ { print $2 } ' "$manifest" | sort -u ) mapfile -t posemath_header_sources < <( find "$posemath_dir" -maxdepth 1 -type f -name '*.h' \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_posemath_header_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "header" && $2 ~ /^src\/libnml\/posemath\/.*\.h$/ { print $2 } ' "$manifest" | sort -u ) mapfile -t posemath_metadata_sources < <( find "$posemath_dir" -maxdepth 1 -type f \ \( -name 'Submakefile' -o -name 'meson.build' \) \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_posemath_metadata_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "metadata" && $2 ~ /^src\/libnml\/posemath\/(Submakefile|meson\.build)$/ { print $2 } ' "$manifest" | sort -u ) missing_posemath_core_sources=() for source in "${posemath_core_sources[@]}"; do if ! printf '%s\n' "${manifest_posemath_core_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_posemath_core_sources+=("$source") fi done if ((${#missing_posemath_core_sources[@]} > 0)); then echo "manifest does not group all LinuxCNC posemath C/C++ sources as core sources: $manifest" >&2 printf 'missing posemath core source: %s\n' "${missing_posemath_core_sources[@]}" >&2 exit 1 fi missing_posemath_header_sources=() for source in "${posemath_header_sources[@]}"; do if ! printf '%s\n' "${manifest_posemath_header_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_posemath_header_sources+=("$source") fi done if ((${#missing_posemath_header_sources[@]} > 0)); then echo "manifest does not group all LinuxCNC posemath headers as header sources: $manifest" >&2 printf 'missing posemath header source: %s\n' "${missing_posemath_header_sources[@]}" >&2 exit 1 fi missing_posemath_metadata_sources=() for source in "${posemath_metadata_sources[@]}"; do if ! printf '%s\n' "${manifest_posemath_metadata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_posemath_metadata_sources+=("$source") fi done if ((${#missing_posemath_metadata_sources[@]} > 0)); then echo "manifest does not group all LinuxCNC posemath build metadata as metadata sources: $manifest" >&2 printf 'missing posemath metadata source: %s\n' "${missing_posemath_metadata_sources[@]}" >&2 exit 1 fi mapfile -t m428_m430_remap_sources < <( find "$linuxcnc_root/configs" -path '*/remap_subs/*' -type f \ \( -name '428remap.ngc' -o -name '429remap.ngc' -o -name '430remap.ngc' \) \ | sed "s#^$linuxcnc_root/##" \ | sort ) mapfile -t manifest_m428_m430_remap_sources < <( awk -F: ' $1 !~ /^($|#)/ && $2 ~ /\/remap_subs\/(428|429|430)remap\.ngc$/ { print $2 } ' "$manifest" | sort -u ) missing_m428_m430_remap_sources=() for source in "${m428_m430_remap_sources[@]}"; do if ! printf '%s\n' "${manifest_m428_m430_remap_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_remap_sources+=("$source") fi done if ((${#missing_m428_m430_remap_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC M428/M429/M430 remap sources: $manifest" >&2 printf 'missing M428/M429/M430 remap source: %s\n' "${missing_m428_m430_remap_sources[@]}" >&2 exit 1 fi mismatched_m428_m430_remap_notes=() for source in "${m428_m430_remap_sources[@]}"; do kinstype=$(sed -n 's/^[[:space:]]*#[[:space:]]*=[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$linuxcnc_root/$source" | head -n 1) manifest_note=$(awk -F: -v path="$source" ' $1 !~ /^($|#)/ && $2 == path { print $3; exit } ' "$manifest") if [[ -z "$kinstype" || "$manifest_note" != *"kinstype $kinstype"* ]]; then mismatched_m428_m430_remap_notes+=("$source") fi done if ((${#mismatched_m428_m430_remap_notes[@]} > 0)); then echo "manifest M428/M429/M430 remap notes do not match LinuxCNC # assignments: $manifest" >&2 printf 'mismatched M428/M429/M430 remap note: %s\n' "${mismatched_m428_m430_remap_notes[@]}" >&2 exit 1 fi mapfile -t m428_m430_ini_sources < <( find "$linuxcnc_root/configs" -name '*.ini' -type f -print0 \ | while IFS= read -r -d '' source; do if grep -Eiq 'REMAP[[:space:]]*=[[:space:]]*M(428|429|430)([^0-9]|$)' "$source"; then printf '%s\n' "${source#"$linuxcnc_root/"}" fi done \ | sort ) mapfile -t manifest_m428_m430_ini_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "config" && $2 ~ /\.ini$/ { print $2 } ' "$manifest" | sort -u ) missing_m428_m430_ini_sources=() for source in "${m428_m430_ini_sources[@]}"; do if ! printf '%s\n' "${manifest_m428_m430_ini_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_ini_sources+=("$source") fi done if ((${#missing_m428_m430_ini_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC INI files that declare M428/M429/M430 remaps: $manifest" >&2 printf 'missing M428/M429/M430 INI source: %s\n' "${missing_m428_m430_ini_sources[@]}" >&2 exit 1 fi mapfile -t manifest_component_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "component" { print $2 } ' "$manifest" | sort -u ) mapfile -t manifest_generated_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "generated" { print $2 } ' "$manifest" | sort -u ) mapfile -t manifest_metadata_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "metadata" { print $2 } ' "$manifest" | sort -u ) m428_m430_kinematics_component_sources=() m428_m430_kinematics_generated_sources=() for source in "${manifest_m428_m430_ini_sources[@]}"; do while IFS= read -r kinematics_name; do [[ -n "$kinematics_name" ]] || continue component_source="src/hal/components/$kinematics_name.comp" generated_source="src/objects/hal/components/$kinematics_name.c" if [[ -f "$linuxcnc_root/$component_source" ]]; then m428_m430_kinematics_component_sources+=("$component_source") fi if [[ -f "$linuxcnc_root/$generated_source" ]]; then m428_m430_kinematics_generated_sources+=("$generated_source") fi done < <( sed -n 's/^[[:space:]]*KINEMATICS[[:space:]]*=[[:space:]]*\([^[:space:]#;]*\).*/\1/Ip' "$linuxcnc_root/$source" ) done mapfile -t m428_m430_kinematics_component_sources < <( printf '%s\n' "${m428_m430_kinematics_component_sources[@]}" | sed '/^$/d' | sort -u ) mapfile -t m428_m430_kinematics_generated_sources < <( printf '%s\n' "${m428_m430_kinematics_generated_sources[@]}" | sed '/^$/d' | sort -u ) missing_m428_m430_kinematics_component_sources=() for source in "${m428_m430_kinematics_component_sources[@]}"; do if ! printf '%s\n' "${manifest_component_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_kinematics_component_sources+=("$source") fi done if ((${#missing_m428_m430_kinematics_component_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC component sources referenced by M428/M429/M430 INI KINEMATICS entries: $manifest" >&2 printf 'missing M428/M429/M430 KINEMATICS component source: %s\n' "${missing_m428_m430_kinematics_component_sources[@]}" >&2 exit 1 fi missing_m428_m430_kinematics_generated_sources=() for source in "${m428_m430_kinematics_generated_sources[@]}"; do if ! printf '%s\n' "${manifest_generated_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_kinematics_generated_sources+=("$source") fi done if ((${#missing_m428_m430_kinematics_generated_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC generated component sources referenced by M428/M429/M430 INI KINEMATICS entries: $manifest" >&2 printf 'missing M428/M429/M430 KINEMATICS generated source: %s\n' "${missing_m428_m430_kinematics_generated_sources[@]}" >&2 exit 1 fi generated_component_metadata_sources=() for source in "${manifest_generated_sources[@]}"; do case "$source" in src/objects/hal/components/*.c) metadata_source=${source%.c}.mak if [[ -f "$linuxcnc_root/$metadata_source" ]]; then generated_component_metadata_sources+=("$metadata_source") fi ;; esac done mapfile -t generated_component_metadata_sources < <( printf '%s\n' "${generated_component_metadata_sources[@]}" | sed '/^$/d' | sort -u ) missing_generated_component_metadata_sources=() for source in "${generated_component_metadata_sources[@]}"; do if ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_generated_component_metadata_sources+=("$source") fi done if ((${#missing_generated_component_metadata_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC generated component make metadata adjacent to generated kinematics sources: $manifest" >&2 printf 'missing generated component make metadata source: %s\n' "${missing_generated_component_metadata_sources[@]}" >&2 exit 1 fi missing_m428_m430_ini_remap_sources=() unresolved_m428_m430_ini_remaps=() for source in "${manifest_m428_m430_ini_sources[@]}"; do config="$linuxcnc_root/$source" config_dir=$(cd "$(dirname "$config")" && pwd) mapfile -t subroutine_paths < <( sed -n 's/^[[:space:]]*SUBROUTINE_PATH[[:space:]]*=[[:space:]]*//Ip' "$config" | sed 's/[[:space:]]*[#;].*$//; s/^[[:space:]]*//; s/[[:space:]]*$//' ) if ((${#subroutine_paths[@]} == 0)); then subroutine_paths=(".") fi for mcode in 428 429 430; do ngc_name=$(sed -n "s/.*REMAP[[:space:]]*=[[:space:]]*M$mcode[[:space:]][^#;]*ngc[[:space:]]*=[[:space:]]*\\([^[:space:]#;]*\\).*/\\1/Ip" "$config" | head -n 1) [[ -n "$ngc_name" ]] || continue [[ "$ngc_name" == *.ngc ]] || ngc_name="$ngc_name.ngc" resolved_source= for subroutine_path in "${subroutine_paths[@]}"; do [[ -n "$subroutine_path" ]] || continue IFS=: read -ra path_parts <<<"$subroutine_path" for path_part in "${path_parts[@]}"; do [[ -n "$path_part" ]] || continue if [[ "$path_part" = /* ]]; then candidate_dir=$path_part else candidate_dir="$config_dir/$path_part" fi if [[ -f "$candidate_dir/$ngc_name" ]]; then resolved_abs=$(cd "$(dirname "$candidate_dir/$ngc_name")" && pwd)/$(basename "$candidate_dir/$ngc_name") resolved_source=${resolved_abs#"$linuxcnc_root/"} break 2 fi done done if [[ -z "$resolved_source" ]]; then unresolved_m428_m430_ini_remaps+=("$source M$mcode ngc=$ngc_name") elif ! printf '%s\n' "${manifest_m428_m430_remap_sources[@]}" | grep -Fx -- "$resolved_source" >/dev/null; then missing_m428_m430_ini_remap_sources+=("$resolved_source") fi done done if ((${#unresolved_m428_m430_ini_remaps[@]} > 0)); then echo "LinuxCNC M428/M429/M430 INI remap sources could not be resolved through SUBROUTINE_PATH: $manifest" >&2 printf 'unresolved M428/M429/M430 INI remap: %s\n' "${unresolved_m428_m430_ini_remaps[@]}" >&2 exit 1 fi if ((${#missing_m428_m430_ini_remap_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC M428/M429/M430 remap sources referenced by INI REMAP entries: $manifest" >&2 printf 'missing INI-referenced M428/M429/M430 remap source: %s\n' "${missing_m428_m430_ini_remap_sources[@]}" >&2 exit 1 fi mapfile -t manifest_config_hal_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "config" && $2 ~ /\.hal$/ { print $2 } ' "$manifest" | sort -u ) unresolved_m428_m430_config_hal_sources=() mapfile -t m428_m430_config_hal_sources < <( for source in "${manifest_m428_m430_ini_sources[@]}"; do config_dir=$(cd "$(dirname "$linuxcnc_root/$source")" && pwd) sed -n 's/^[[:space:]]*\(HALFILE\|POSTGUI_HALFILE\)[[:space:]]*=[[:space:]]*//Ip' "$linuxcnc_root/$source" | sed 's/[[:space:]]*[#;].*$//; s/^[[:space:]]*//; s/[[:space:]]*$//' | while IFS= read -r hal_source; do [[ -n "$hal_source" && "$hal_source" != LIB:* ]] || continue if [[ "$hal_source" = /* ]]; then candidate=$hal_source else candidate="$config_dir/$hal_source" fi if [[ -f "$candidate" ]]; then candidate_abs=$(cd "$(dirname "$candidate")" && pwd)/$(basename "$candidate") printf '%s\n' "${candidate_abs#"$linuxcnc_root/"}" else printf '%s HALFILE=%s\n' "$source" "$hal_source" >&2 fi done done | sort -u ) mapfile -t unresolved_m428_m430_config_hal_sources < <( for source in "${manifest_m428_m430_ini_sources[@]}"; do config_dir=$(cd "$(dirname "$linuxcnc_root/$source")" && pwd) sed -n 's/^[[:space:]]*\(HALFILE\|POSTGUI_HALFILE\)[[:space:]]*=[[:space:]]*//Ip' "$linuxcnc_root/$source" | sed 's/[[:space:]]*[#;].*$//; s/^[[:space:]]*//; s/[[:space:]]*$//' | while IFS= read -r hal_source; do [[ -n "$hal_source" && "$hal_source" != LIB:* ]] || continue if [[ "$hal_source" = /* ]]; then candidate=$hal_source else candidate="$config_dir/$hal_source" fi [[ -f "$candidate" ]] || printf '%s HALFILE=%s\n' "$source" "$hal_source" done done | sort -u ) if ((${#unresolved_m428_m430_config_hal_sources[@]} > 0)); then echo "LinuxCNC M428/M429/M430 INI HALFILE/POSTGUI_HALFILE sources could not be resolved: $manifest" >&2 printf 'unresolved M428/M429/M430 HAL source: %s\n' "${unresolved_m428_m430_config_hal_sources[@]}" >&2 exit 1 fi missing_m428_m430_config_hal_sources=() for source in "${m428_m430_config_hal_sources[@]}"; do if ! printf '%s\n' "${manifest_config_hal_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_config_hal_sources+=("$source") fi done if ((${#missing_m428_m430_config_hal_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC HALFILE/POSTGUI_HALFILE sources referenced by M428/M429/M430 INI files: $manifest" >&2 printf 'missing M428/M429/M430 HAL source: %s\n' "${missing_m428_m430_config_hal_sources[@]}" >&2 exit 1 fi mapfile -t manifest_asset_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "asset" { print $2 } ' "$manifest" | sort -u ) mapfile -t manifest_tooldata_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "tooldata" { print $2 } ' "$manifest" | sort -u ) required_delta_asset_sources=( configs/sim/axis/ldelta.ini configs/sim/axis/ldelta_demo.ini configs/sim/axis/ldelta_demo.txt configs/sim/axis/ldelta_demo_es.txt configs/sim/axis/rdelta.ini lib/hallib/sim_ldelta.hal lib/hallib/sim_rdelta.hal ) missing_delta_asset_sources=() for source in "${required_delta_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_delta_asset_sources+=("$source") fi done if ((${#missing_delta_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC delta kinematics INI/HAL demo sources as asset sources: $manifest" >&2 printf 'missing delta kinematics asset source: %s\n' "${missing_delta_asset_sources[@]}" >&2 exit 1 fi required_corexy_asset_sources=( configs/sim/axis/corexy/corexy_by_kins.ini configs/sim/axis/corexy/corexy_by_hal.ini configs/sim/axis/corexy/corexy_base.inc configs/sim/axis/corexy/3joint_xyz.hal configs/sim/axis/corexy/corexy_by_hal.hal configs/sim/axis/corexy/corexy.halshow configs/sim/axis/corexy/README configs/sim/axis/corexy/corexy_by_kins.txt configs/sim/axis/corexy/corexy_by_hal.txt ) missing_corexy_asset_sources=() for source in "${required_corexy_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_corexy_asset_sources+=("$source") fi done if ((${#missing_corexy_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC CoreXY kinematics INI/HAL/demo sources as asset sources: $manifest" >&2 printf 'missing CoreXY kinematics asset source: %s\n' "${missing_corexy_asset_sources[@]}" >&2 exit 1 fi required_corexy_component_source=src/hal/components/corexy_by_hal.comp if ! printf '%s\n' "${manifest_component_sources[@]}" | grep -Fx -- "$required_corexy_component_source" >/dev/null; then echo "manifest does not cover LinuxCNC CoreXY HAL component source: $manifest" >&2 printf 'missing CoreXY component source: %s\n' "$required_corexy_component_source" >&2 exit 1 fi required_corexy_generated_source=src/objects/hal/components/corexy_by_hal.c if ! printf '%s\n' "${manifest_generated_sources[@]}" | grep -Fx -- "$required_corexy_generated_source" >/dev/null; then echo "manifest does not cover LinuxCNC generated CoreXY HAL component source: $manifest" >&2 printf 'missing generated CoreXY component source: %s\n' "$required_corexy_generated_source" >&2 exit 1 fi required_corexy_metadata_sources=( src/objects/hal/components/corexy_by_hal.mak src/objects/man/man9/corexy_by_hal.9.adoc ) missing_corexy_metadata_sources=() for source in "${required_corexy_metadata_sources[@]}"; do if ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_corexy_metadata_sources+=("$source") fi done if ((${#missing_corexy_metadata_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC CoreXY HAL component metadata sources: $manifest" >&2 printf 'missing CoreXY metadata source: %s\n' "${missing_corexy_metadata_sources[@]}" >&2 exit 1 fi required_rose_engine_asset_sources=( configs/sim/axis/rose_engine/rose_engine.ini configs/sim/axis/rose_engine/rose_engine.halshow configs/sim/axis/rose_engine/rcone.ngc configs/sim/axis/rose_engine/rcone_demo.ngc configs/sim/axis/rose_engine/README ) missing_rose_engine_asset_sources=() for source in "${required_rose_engine_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_rose_engine_asset_sources+=("$source") fi done if ((${#missing_rose_engine_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC rose engine kinematics INI/NGC/demo sources as asset sources: $manifest" >&2 printf 'missing rose engine kinematics asset source: %s\n' "${missing_rose_engine_asset_sources[@]}" >&2 exit 1 fi required_rosekins_metadata_source=docs/src/man/man9/rosekins.9.adoc if ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$required_rosekins_metadata_source" >/dev/null; then echo "manifest does not cover LinuxCNC rosekins manpage source: $manifest" >&2 printf 'missing rosekins metadata source: %s\n' "$required_rosekins_metadata_source" >&2 exit 1 fi required_tripod_asset_sources=( configs/sim/tklinuxcnc/tripod.ini lib/hallib/tripodsim.hal configs/sim/tklinuxcnc/README ) missing_tripod_asset_sources=() for source in "${required_tripod_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_tripod_asset_sources+=("$source") fi done if ((${#missing_tripod_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC tripod kinematics INI/HAL sources as asset sources: $manifest" >&2 printf 'missing tripod kinematics asset source: %s\n' "${missing_tripod_asset_sources[@]}" >&2 exit 1 fi required_tripod_metadata_source=docs/man/man9/tripodkins.9 if ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$required_tripod_metadata_source" >/dev/null; then echo "manifest does not cover LinuxCNC tripodkins manpage source: $manifest" >&2 printf 'missing tripodkins metadata source: %s\n' "$required_tripod_metadata_source" >&2 exit 1 fi required_tklinuxcnc_trivkins_asset_sources=( configs/sim/tklinuxcnc/trivkins/xz.ini configs/sim/tklinuxcnc/trivkins/xz_both.ini configs/sim/tklinuxcnc/trivkins/xzbase.inc configs/sim/tklinuxcnc/trivkins/xz.tbl configs/sim/tklinuxcnc/trivkins/README ) missing_tklinuxcnc_trivkins_asset_sources=() for source in "${required_tklinuxcnc_trivkins_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_tklinuxcnc_trivkins_asset_sources+=("$source") fi done if ((${#missing_tklinuxcnc_trivkins_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC TkLinuxCNC trivkins XZ INI/include/tool/demo sources as asset sources: $manifest" >&2 printf 'missing TkLinuxCNC trivkins asset source: %s\n' "${missing_tklinuxcnc_trivkins_asset_sources[@]}" >&2 exit 1 fi required_axis_ja_xz_trivkins_asset_sources=( configs/sim/axis/ja_tests/xz/xz.ini configs/sim/axis/ja_tests/xz/xz_both.ini configs/sim/axis/ja_tests/xz/xz_both_auto.ini configs/sim/axis/ja_tests/xz/xzbase.inc configs/sim/axis/ja_tests/xz/xz.tbl configs/sim/axis/ja_tests/xz/README ) missing_axis_ja_xz_trivkins_asset_sources=() for source in "${required_axis_ja_xz_trivkins_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_axis_ja_xz_trivkins_asset_sources+=("$source") fi done if ((${#missing_axis_ja_xz_trivkins_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC AXIS joint-axis trivkins XZ INI/include/tool/demo sources as asset sources: $manifest" >&2 printf 'missing AXIS joint-axis trivkins asset source: %s\n' "${missing_axis_ja_xz_trivkins_asset_sources[@]}" >&2 exit 1 fi required_axis_ja_xyzb_locking_indexer_asset_sources=( configs/sim/axis/ja_tests/xyzb_locking_indexer/xyzb.ini configs/sim/axis/ja_tests/xyzb_locking_indexer/xyyzb.ini configs/sim/axis/ja_tests/xyzb_locking_indexer/xyzb.hal configs/sim/axis/ja_tests/xyzb_locking_indexer/xyzb.halshow configs/sim/axis/ja_tests/xyzb_locking_indexer/README ) missing_axis_ja_xyzb_locking_indexer_asset_sources=() for source in "${required_axis_ja_xyzb_locking_indexer_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_axis_ja_xyzb_locking_indexer_asset_sources+=("$source") fi done if ((${#missing_axis_ja_xyzb_locking_indexer_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC AXIS joint-axis trivkins XYZB locking indexer INI/HAL/demo sources as asset sources: $manifest" >&2 printf 'missing AXIS joint-axis XYZB locking indexer asset source: %s\n' "${missing_axis_ja_xyzb_locking_indexer_asset_sources[@]}" >&2 exit 1 fi required_scorbot_asset_sources=( configs/by_machine/scorbot-er-3/scorbot-er-3.ini configs/by_machine/scorbot-er-3/scorbot-er-3.hal configs/by_machine/scorbot-er-3/pyvcp.hal configs/by_machine/scorbot-er-3/gripper.xml src/hal/user_comps/scorbot-er-3.py ) missing_scorbot_asset_sources=() for source in "${required_scorbot_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_scorbot_asset_sources+=("$source") fi done if ((${#missing_scorbot_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC Scorbot ER-3 kinematics INI/HAL/PYVCP/user component sources as asset sources: $manifest" >&2 printf 'missing Scorbot ER-3 asset source: %s\n' "${missing_scorbot_asset_sources[@]}" >&2 exit 1 fi required_scorbot_metadata_sources=( src/hal/user_comps/Submakefile docs/src/man/man1/scorbot-er-3.1.adoc ) missing_scorbot_metadata_sources=() for source in "${required_scorbot_metadata_sources[@]}"; do if ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_scorbot_metadata_sources+=("$source") fi done if ((${#missing_scorbot_metadata_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC Scorbot ER-3 user component metadata sources: $manifest" >&2 printf 'missing Scorbot ER-3 metadata source: %s\n' "${missing_scorbot_metadata_sources[@]}" >&2 exit 1 fi required_gantry_asset_sources=( configs/sim/axis/gantry/gantry_mm.ini configs/sim/axis/gantry/gantry_mm.hal configs/sim/axis/gantry/gantry_mm.tbl configs/sim/axis/gantry/README ) missing_gantry_asset_sources=() for source in "${required_gantry_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_gantry_asset_sources+=("$source") fi done if ((${#missing_gantry_asset_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC gantry metric INI/HAL/tool sources as asset sources: $manifest" >&2 printf 'missing gantry metric asset source: %s\n' "${missing_gantry_asset_sources[@]}" >&2 exit 1 fi required_gantry_component_source=src/hal/components/gantry.comp if ! printf '%s\n' "${manifest_component_sources[@]}" | grep -Fx -- "$required_gantry_component_source" >/dev/null; then echo "manifest does not cover LinuxCNC gantry HAL component source: $manifest" >&2 printf 'missing gantry component source: %s\n' "$required_gantry_component_source" >&2 exit 1 fi required_gantry_generated_source=src/objects/hal/components/gantry.c if ! printf '%s\n' "${manifest_generated_sources[@]}" | grep -Fx -- "$required_gantry_generated_source" >/dev/null; then echo "manifest does not cover LinuxCNC generated gantry HAL component source: $manifest" >&2 printf 'missing generated gantry component source: %s\n' "$required_gantry_generated_source" >&2 exit 1 fi required_gantry_metadata_sources=( src/objects/hal/components/gantry.mak src/objects/man/man9/gantry.9.adoc ) missing_gantry_metadata_sources=() for source in "${required_gantry_metadata_sources[@]}"; do if ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_gantry_metadata_sources+=("$source") fi done if ((${#missing_gantry_metadata_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC gantry HAL component metadata sources: $manifest" >&2 printf 'missing gantry metadata source: %s\n' "${missing_gantry_metadata_sources[@]}" >&2 exit 1 fi resolve_vismach_loadusr_source() { local command_name=$1 case "$command_name" in */*|*\\*|*.py) return 0 ;; esac local vismach_source="src/hal/user_comps/vismach/$command_name.py" if [[ -f "$linuxcnc_root/$vismach_source" ]]; then printf '%s\n' "$vismach_source" fi } m428_m430_vismach_loadusr_sources=() for source in "${manifest_m428_m430_ini_sources[@]}"; do while IFS= read -r command_name; do [[ -n "$command_name" ]] || continue while IFS= read -r vismach_source; do [[ -n "$vismach_source" ]] && m428_m430_vismach_loadusr_sources+=("$vismach_source") done < <(resolve_vismach_loadusr_source "$command_name") done < <( 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 for source in "${m428_m430_config_hal_sources[@]}"; do while IFS= read -r command_name; do [[ -n "$command_name" ]] || continue while IFS= read -r vismach_source; do [[ -n "$vismach_source" ]] && m428_m430_vismach_loadusr_sources+=("$vismach_source") done < <(resolve_vismach_loadusr_source "$command_name") done < <( 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 mapfile -t m428_m430_vismach_loadusr_sources < <( printf '%s\n' "${m428_m430_vismach_loadusr_sources[@]}" | sed '/^$/d' | sort -u ) missing_m428_m430_vismach_loadusr_sources=() for source in "${m428_m430_vismach_loadusr_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_vismach_loadusr_sources+=("$source") fi done if ((${#missing_m428_m430_vismach_loadusr_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC vismach user component sources referenced by M428/M429/M430 INI/HAL loadusr commands as asset sources: $manifest" >&2 printf 'missing M428/M429/M430 vismach loadusr source: %s\n' "${missing_m428_m430_vismach_loadusr_sources[@]}" >&2 exit 1 fi if ((${#m428_m430_vismach_loadusr_sources[@]} > 0)); then vismach_submakefile_source=src/hal/user_comps/vismach/Submakefile if [[ -f "$linuxcnc_root/$vismach_submakefile_source" ]] && ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$vismach_submakefile_source" >/dev/null; then echo "manifest does not cover LinuxCNC vismach user component build metadata as metadata sources: $manifest" >&2 printf 'missing vismach user component metadata source: %s\n' "$vismach_submakefile_source" >&2 exit 1 fi fi m428_m430_vismach_man_sources=() for source in "${m428_m430_vismach_loadusr_sources[@]}"; do command_name=$(basename "$source" .py) man_source="docs/src/man/man1/$command_name.1.adoc" if [[ -f "$linuxcnc_root/$man_source" ]]; then m428_m430_vismach_man_sources+=("$man_source") fi done mapfile -t m428_m430_vismach_man_sources < <( printf '%s\n' "${m428_m430_vismach_man_sources[@]}" | sed '/^$/d' | sort -u ) missing_m428_m430_vismach_man_sources=() for source in "${m428_m430_vismach_man_sources[@]}"; do if ! printf '%s\n' "${manifest_metadata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_vismach_man_sources+=("$source") fi done if ((${#missing_m428_m430_vismach_man_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC vismach user component manpage sources as metadata sources: $manifest" >&2 printf 'missing vismach user component manpage source: %s\n' "${missing_m428_m430_vismach_man_sources[@]}" >&2 exit 1 fi qtvcp_panel_sources=() for source in "${manifest_m428_m430_ini_sources[@]}"; do if grep -Eiq '^[[:space:]]*EMBED_TAB_COMMAND[[:space:]]*=[[:space:]]*qtvcp[[:space:]]+vismach_scara([[:space:]#;]|$)' "$linuxcnc_root/$source"; then qtvcp_panel_sources+=( share/qtvcp/panels/vismach_scara/vismach_scara.ui share/qtvcp/panels/vismach_scara/vismach_scara_handler.py lib/python/qtvcp/lib/qt_vismach/README.txt lib/python/qtvcp/lib/qt_vismach/__init__.py lib/python/qtvcp/lib/qt_vismach/primitives.py lib/python/qtvcp/lib/qt_vismach/qt_vismach.py lib/python/qtvcp/lib/qt_vismach/scara.py ) fi done mapfile -t qtvcp_panel_sources < <( printf '%s\n' "${qtvcp_panel_sources[@]}" | sed '/^$/d' | sort -u ) missing_qtvcp_panel_sources=() for source in "${qtvcp_panel_sources[@]}"; do if [[ ! -f "$linuxcnc_root/$source" ]]; then missing_qtvcp_panel_sources+=("$source") elif ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_qtvcp_panel_sources+=("$source") fi done if ((${#missing_qtvcp_panel_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC QtVCP vismach_scara sources referenced by M428/M429/M430 INI EMBED_TAB_COMMAND entries as asset sources: $manifest" >&2 printf 'missing M428/M429/M430 QtVCP panel source: %s\n' "${missing_qtvcp_panel_sources[@]}" >&2 exit 1 fi m428_m430_config_asset_sources=() unresolved_m428_m430_config_asset_sources=() for source in "${manifest_m428_m430_ini_sources[@]}"; do config_dir=$(cd "$(dirname "$linuxcnc_root/$source")" && pwd) while IFS= read -r config_asset; do key=${config_asset%%=*} asset_source=${config_asset#*=} [[ -n "$asset_source" ]] || continue if [[ "$asset_source" = /* ]]; then candidate=$asset_source else candidate="$config_dir/$asset_source" fi if [[ -f "$candidate" ]]; then candidate_abs=$(cd "$(dirname "$candidate")" && pwd)/$(basename "$candidate") case "$candidate_abs" in "$linuxcnc_root"/*) m428_m430_config_asset_sources+=("${candidate_abs#"$linuxcnc_root/"}") ;; *) unresolved_m428_m430_config_asset_sources+=("$source $key=$asset_source") ;; esac else unresolved_m428_m430_config_asset_sources+=("$source $key=$asset_source") fi done < <( awk -F= ' BEGIN { IGNORECASE = 1 } /^[[:space:]]*(PYVCP|OPEN_FILE)[[:space:]]*=/ { key = $1 value = $0 sub(/^[^=]*=/, "", value) sub(/[[:space:]]*[#;].*$/, "", value) gsub(/^[[:space:]]+|[[:space:]]+$/, "", key) gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) gsub(/^"|"$/, "", value) if (value != "") { print toupper(key) "=" value } } ' "$linuxcnc_root/$source" ) done mapfile -t m428_m430_config_asset_sources < <( printf '%s\n' "${m428_m430_config_asset_sources[@]}" | sed '/^$/d' | sort -u ) mapfile -t unresolved_m428_m430_config_asset_sources < <( printf '%s\n' "${unresolved_m428_m430_config_asset_sources[@]}" | sed '/^$/d' | sort -u ) if ((${#unresolved_m428_m430_config_asset_sources[@]} > 0)); then echo "LinuxCNC M428/M429/M430 INI PYVCP/OPEN_FILE assets could not be resolved: $manifest" >&2 printf 'unresolved M428/M429/M430 config asset: %s\n' "${unresolved_m428_m430_config_asset_sources[@]}" >&2 exit 1 fi missing_m428_m430_config_asset_sources=() for source in "${m428_m430_config_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_config_asset_sources+=("$source") fi done if ((${#missing_m428_m430_config_asset_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC PYVCP/OPEN_FILE assets referenced by M428/M429/M430 INI files as asset sources: $manifest" >&2 printf 'missing M428/M429/M430 config asset source: %s\n' "${missing_m428_m430_config_asset_sources[@]}" >&2 exit 1 fi m428_m430_config_readme_sources=() for source in "${manifest_m428_m430_ini_sources[@]}"; do config_dir=$(cd "$(dirname "$linuxcnc_root/$source")" && pwd) readme_source="$config_dir/README" if [[ -f "$readme_source" ]]; then readme_abs=$(cd "$(dirname "$readme_source")" && pwd)/$(basename "$readme_source") m428_m430_config_readme_sources+=("${readme_abs#"$linuxcnc_root/"}") fi done mapfile -t m428_m430_config_readme_sources < <( printf '%s\n' "${m428_m430_config_readme_sources[@]}" | sed '/^$/d' | sort -u ) missing_m428_m430_config_readme_sources=() for source in "${m428_m430_config_readme_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_config_readme_sources+=("$source") fi done if ((${#missing_m428_m430_config_readme_sources[@]} > 0)); then echo "manifest does not cover LinuxCNC README sources adjacent to M428/M429/M430 INI files as asset sources: $manifest" >&2 printf 'missing M428/M429/M430 README source: %s\n' "${missing_m428_m430_config_readme_sources[@]}" >&2 exit 1 fi m428_m430_config_tooldata_sources=() unresolved_m428_m430_config_tooldata_sources=() for source in "${manifest_m428_m430_ini_sources[@]}"; do config_dir=$(cd "$(dirname "$linuxcnc_root/$source")" && pwd) while IFS= read -r tool_table; do [[ -n "$tool_table" ]] || continue if [[ "$tool_table" = /* ]]; then candidate=$tool_table else candidate="$config_dir/$tool_table" fi if [[ -f "$candidate" ]]; then candidate_abs=$(cd "$(dirname "$candidate")" && pwd)/$(basename "$candidate") case "$candidate_abs" in "$linuxcnc_root"/*) m428_m430_config_tooldata_sources+=("${candidate_abs#"$linuxcnc_root/"}") ;; *) unresolved_m428_m430_config_tooldata_sources+=("$source TOOL_TABLE=$tool_table") ;; esac else unresolved_m428_m430_config_tooldata_sources+=("$source TOOL_TABLE=$tool_table") fi done < <( awk -F= ' BEGIN { IGNORECASE = 1 } /^[[:space:]]*TOOL_TABLE[[:space:]]*=/ { value = $0 sub(/^[^=]*=/, "", value) sub(/[[:space:]]*[#;].*$/, "", value) gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) gsub(/^"|"$/, "", value) if (value != "") print value } ' "$linuxcnc_root/$source" ) done mapfile -t m428_m430_config_tooldata_sources < <( printf '%s\n' "${m428_m430_config_tooldata_sources[@]}" | sed '/^$/d' | sort -u ) mapfile -t unresolved_m428_m430_config_tooldata_sources < <( printf '%s\n' "${unresolved_m428_m430_config_tooldata_sources[@]}" | sed '/^$/d' | sort -u ) missing_m428_m430_config_tooldata_sources=() for source in "${m428_m430_config_tooldata_sources[@]}"; do if ! printf '%s\n' "${manifest_tooldata_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_m428_m430_config_tooldata_sources+=("$source") fi done if ((${#missing_m428_m430_config_tooldata_sources[@]} > 0)); then echo "manifest does not cover all resolvable LinuxCNC TOOL_TABLE files referenced by M428/M429/M430 INI files as tooldata sources: $manifest" >&2 printf 'missing M428/M429/M430 tool table source: %s\n' "${missing_m428_m430_config_tooldata_sources[@]}" >&2 exit 1 fi mapfile -t manifest_twp_ini_sources < <( awk -F: ' $1 !~ /^($|#)/ && $1 == "config" && $2 ~ /table-rotary_spindle-rotary-nutating\/.*_twp\/.*\.ini$/ { print $2 } ' "$manifest" | sort -u ) twp_config_asset_sources=() unresolved_twp_config_asset_sources=() resolve_twp_config_asset() { local config_dir=$1 local source=$2 local key=$3 local asset_source=$4 local candidate if [[ "$asset_source" = /* ]]; then candidate=$asset_source else candidate="$config_dir/$asset_source" fi if [[ -f "$candidate" ]]; then local candidate_abs candidate_abs=$(cd "$(dirname "$candidate")" && pwd)/$(basename "$candidate") case "$candidate_abs" in "$linuxcnc_root"/*) twp_config_asset_sources+=("${candidate_abs#"$linuxcnc_root/"}") ;; *) unresolved_twp_config_asset_sources+=("$source $key=$asset_source") ;; esac else unresolved_twp_config_asset_sources+=("$source $key=$asset_source") fi } resolve_twp_subroutine_asset() { local config_dir=$1 local source=$2 local key=$3 local ngc_name=$4 shift 4 local subroutine_paths=("$@") [[ "$ngc_name" == *.ngc ]] || ngc_name="$ngc_name.ngc" local resolved_source= local subroutine_path path_part candidate_dir candidate resolved_abs for subroutine_path in "${subroutine_paths[@]}"; do [[ -n "$subroutine_path" ]] || continue IFS=: read -ra path_parts <<<"$subroutine_path" for path_part in "${path_parts[@]}"; do [[ -n "$path_part" ]] || continue if [[ "$path_part" = /* ]]; then candidate_dir=$path_part else candidate_dir="$config_dir/$path_part" fi candidate="$candidate_dir/$ngc_name" if [[ -f "$candidate" ]]; then resolved_abs=$(cd "$(dirname "$candidate")" && pwd)/$(basename "$candidate") resolved_source=${resolved_abs#"$linuxcnc_root/"} break 2 fi done done if [[ -n "$resolved_source" ]]; then twp_config_asset_sources+=("$resolved_source") else unresolved_twp_config_asset_sources+=("$source $key=$ngc_name") fi } for source in "${manifest_twp_ini_sources[@]}"; do config="$linuxcnc_root/$source" config_dir=$(cd "$(dirname "$config")" && pwd) mapfile -t subroutine_paths < <( sed -n 's/^[[:space:]]*SUBROUTINE_PATH[[:space:]]*=[[:space:]]*//Ip' "$config" | sed 's/[[:space:]]*[#;].*$//; s/^[[:space:]]*//; s/[[:space:]]*$//' ) if ((${#subroutine_paths[@]} == 0)); then subroutine_paths=(".") fi while IFS='|' read -r remap_code ngc_name; do [[ -n "$remap_code" && -n "$ngc_name" ]] || continue case "$remap_code" in M428|m428|M429|m429|M430|m430) continue ;; esac resolve_twp_subroutine_asset "$config_dir" "$source" "REMAP $remap_code ngc" "$ngc_name" "${subroutine_paths[@]}" done < <( awk ' BEGIN { IGNORECASE = 1 } /^[[:space:]]*REMAP[[:space:]]*=/ && /[[:space:]]ngc[[:space:]]*=/ { code = $0 ngc = $0 sub(/.*REMAP[[:space:]]*=[[:space:]]*/, "", code) sub(/[[:space:]].*/, "", code) sub(/.*[[:space:]]ngc[[:space:]]*=[[:space:]]*/, "", ngc) sub(/[[:space:]#;].*/, "", ngc) print code "|" ngc } ' "$config" ) while IFS= read -r abort_name; do [[ -n "$abort_name" ]] || continue resolve_twp_subroutine_asset "$config_dir" "$source" "ON_ABORT_COMMAND" "$abort_name" "${subroutine_paths[@]}" done < <( sed -n 's/^[[:space:]]*ON_ABORT_COMMAND[[:space:]]*=[[:space:]]*o[[:space:]]*<\([^>]*\)>.*/\1/Ip' "$config" ) while IFS= read -r twp_asset; do [[ -n "$twp_asset" ]] || continue resolve_twp_config_asset "$config_dir" "$source" "TWP asset" "$twp_asset" done < <( awk -F= ' BEGIN { IGNORECASE = 1 } /^[[:space:]]*TOPLEVEL[[:space:]]*=/ { value = $0 sub(/^[^=]*=/, "", value) sub(/[[:space:]]*[#;].*$/, "", value) gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) if (value != "") print value } /^[[:space:]]*EMBED_TAB_COMMAND[[:space:]]*=/ && /pyvcp[[:space:]]/ { for (i = 1; i <= NF; i++) { if ($i == "pyvcp" && (i + 1) <= NF) { value = $(i + 1) sub(/[[:space:]#;].*$/, "", value) if (value != "") print value } } } /^[[:space:]]*TOOL_TABLE[[:space:]]*=/ { next } /^[[:space:]]*HALCMD[[:space:]]*=/ && /loadusr/ { for (i = 1; i <= NF; i++) { if ($i ~ /^\.\.\/.*\.py$/) print $i } } ' "$config" ) mapfile -t twp_loaded_python_sources < <( awk ' BEGIN { IGNORECASE = 1 } /^[[:space:]]*TOPLEVEL[[:space:]]*=/ { value = $0 sub(/^[^=]*=/, "", value) sub(/[[:space:]]*[#;].*$/, "", value) gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) if (value != "") print value } /^[[:space:]]*HALCMD[[:space:]]*=/ && /loadusr/ { for (i = 1; i <= NF; i++) { if ($i ~ /^\.\.\/.*\.py$/) print $i } } ' "$config" ) for twp_python_source in "${twp_loaded_python_sources[@]}"; do python_candidate="$config_dir/$twp_python_source" if [[ ! -f "$python_candidate" ]]; then continue fi python_abs=$(cd "$(dirname "$python_candidate")" && pwd)/$(basename "$python_candidate") python_dir=$(dirname "$python_abs") if grep -Eq '^[[:space:]]*import[[:space:]]+remap([[:space:]]|$)' "$python_abs"; then resolve_twp_config_asset "$python_dir" "$source" "python import" "remap.py" fi if grep -Eq '^[[:space:]]*from[[:space:]]+util[[:space:]]+import([[:space:]]|$)' "$python_abs"; then resolve_twp_config_asset "$python_dir" "$source" "python import" "util.py" fi if grep -Eq '^[[:space:]]*from[[:space:]]+twp_vismach[[:space:]]+import([[:space:]]|$)' "$python_abs"; then resolve_twp_config_asset "$python_dir" "$source" "python import" "twp_vismach.py" fi if grep -F 'work_piece_1.stl' "$python_abs" >/dev/null; then resolve_twp_config_asset "$config_dir" "$source" "vismach STL" "work_piece_1.stl" fi done done mapfile -t twp_config_asset_sources < <( printf '%s\n' "${twp_config_asset_sources[@]}" | sed '/^$/d' | sort -u ) mapfile -t unresolved_twp_config_asset_sources < <( printf '%s\n' "${unresolved_twp_config_asset_sources[@]}" | sed '/^$/d' | sort -u ) if ((${#unresolved_twp_config_asset_sources[@]} > 0)); then echo "LinuxCNC TWP INI source assets could not be resolved: $manifest" >&2 printf 'unresolved TWP config asset: %s\n' "${unresolved_twp_config_asset_sources[@]}" >&2 exit 1 fi missing_twp_config_asset_sources=() for source in "${twp_config_asset_sources[@]}"; do if ! printf '%s\n' "${manifest_asset_sources[@]}" | grep -Fx -- "$source" >/dev/null; then missing_twp_config_asset_sources+=("$source") fi done if ((${#missing_twp_config_asset_sources[@]} > 0)); then echo "manifest does not cover all LinuxCNC TWP INI source assets as asset sources: $manifest" >&2 printf 'missing TWP config asset source: %s\n' "${missing_twp_config_asset_sources[@]}" >&2 exit 1 fi fi