190 lines
6.8 KiB
Bash
Executable File
190 lines
6.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
LINUXCNC_ROOT="${LINUXCNC_ROOT:-$ROOT_DIR/../linuxcnc}"
|
|
FIXTURE_FAMILY="axis/remap/stop-lookahead/nc_files"
|
|
FIXTURE_DIR="$LINUXCNC_ROOT/configs/sim/axis/remap/stop-lookahead"
|
|
FIXTURE_INI="$FIXTURE_DIR/demo.ini"
|
|
RUN_DIR="$ROOT_DIR/build/native/python-remap-runtime"
|
|
PYTHON_RUNTIME_STDOUT="$RUN_DIR/python_lifecycle.stdout.log"
|
|
PYTHON_RUNTIME_STDERR="$RUN_DIR/python_lifecycle.stderr.log"
|
|
|
|
required_commands=(python3 linuxcnc)
|
|
missing_requirements=()
|
|
|
|
command_path() {
|
|
command -v "$1" 2>/dev/null || true
|
|
}
|
|
|
|
csv_join() {
|
|
local IFS=,
|
|
printf '%s' "$*"
|
|
}
|
|
|
|
print_kv() {
|
|
printf '%s=%s\n' "$1" "$2"
|
|
}
|
|
|
|
runtime_requirements=()
|
|
for command_name in "${required_commands[@]}"; do
|
|
path="$(command_path "$command_name")"
|
|
if [[ -n "$path" ]]; then
|
|
runtime_requirements+=("$command_name:1")
|
|
print_kv "python_remap_runtime_${command_name}_path" "$path"
|
|
else
|
|
runtime_requirements+=("$command_name:0")
|
|
missing_requirements+=("$command_name")
|
|
fi
|
|
done
|
|
|
|
source_ready=1
|
|
for rel in \
|
|
src/emc/rs274ngc/interp_python.cc \
|
|
src/emc/pythonplugin/python_plugin.cc \
|
|
configs/sim/axis/remap/stop-lookahead/demo.ini \
|
|
configs/sim/axis/remap/stop-lookahead/python/remap.py \
|
|
configs/sim/axis/remap/stop-lookahead/python/toplevel.py; do
|
|
if [[ ! -f "$LINUXCNC_ROOT/$rel" ]]; then
|
|
source_ready=0
|
|
missing_requirements+=("$rel")
|
|
print_kv "python_remap_missing_source_${rel//[^A-Za-z0-9]/_}" "$LINUXCNC_ROOT/$rel"
|
|
fi
|
|
done
|
|
|
|
runtime_ready=0
|
|
if [[ "${#missing_requirements[@]}" -eq 0 ]]; then
|
|
runtime_ready=1
|
|
fi
|
|
|
|
print_kv python_remap_runtime_fixture_family "$FIXTURE_FAMILY"
|
|
print_kv python_remap_runtime_fixture_id "stop_lookahead_python_runtime_lifecycle"
|
|
print_kv python_remap_runtime_fixture_scope "no_python_callables_no_ngc_only_subpaths"
|
|
print_kv python_remap_runtime_phases "initialize_python,apply_ini_python_path,execute_toplevel,callable_lookup,pycall_dispatch,callable_invoke,remap_phase_dispatch,generator_finish,execute_python_runtime,reload_on_change"
|
|
print_kv python_remap_runtime_modules "axis/remap/stop-lookahead/python/remap.py,axis/remap/stop-lookahead/python/toplevel.py"
|
|
print_kv python_remap_runtime_requirements "$(csv_join "${runtime_requirements[@]}")"
|
|
if [[ "${#missing_requirements[@]}" -eq 0 ]]; then
|
|
print_kv python_remap_missing_requirements "-"
|
|
else
|
|
print_kv python_remap_missing_requirements "$(csv_join "${missing_requirements[@]}")"
|
|
fi
|
|
print_kv python_remap_source_proof_ready "$source_ready"
|
|
print_kv python_remap_runtime_ready "$runtime_ready"
|
|
print_kv python_remap_execution_enabled 0
|
|
print_kv python_remap_promotion_allowed 0
|
|
|
|
if [[ "$source_ready" != "1" ]]; then
|
|
print_kv python_remap_runtime_probe_status "blocked_missing_source"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "$runtime_ready" != "1" ]]; then
|
|
print_kv python_remap_runtime_probe_status "skipped_missing_host_runtime"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "${ENABLE_PYTHON_REMAP_RUNTIME_PROBE:-0}" != "1" ]]; then
|
|
print_kv python_remap_runtime_probe_status "ready_disabled_by_default"
|
|
print_kv python_remap_runtime_probe_note "set_ENABLE_PYTHON_REMAP_RUNTIME_PROBE_1_to_run_python_lifecycle_probe"
|
|
exit 0
|
|
fi
|
|
|
|
mkdir -p "$RUN_DIR"
|
|
rm -f "$PYTHON_RUNTIME_STDOUT" "$PYTHON_RUNTIME_STDERR"
|
|
|
|
print_kv python_remap_runtime_probe_fixture "$FIXTURE_DIR"
|
|
print_kv python_remap_runtime_probe_ini "$FIXTURE_INI"
|
|
print_kv python_remap_runtime_probe_stdout "$PYTHON_RUNTIME_STDOUT"
|
|
print_kv python_remap_runtime_probe_stderr "$PYTHON_RUNTIME_STDERR"
|
|
|
|
if python3 - "$LINUXCNC_ROOT" "$FIXTURE_DIR" "$FIXTURE_INI" "$RUN_DIR" \
|
|
>"$PYTHON_RUNTIME_STDOUT" 2>"$PYTHON_RUNTIME_STDERR" <<'PY'
|
|
import configparser
|
|
import importlib
|
|
import inspect
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
|
|
linuxcnc_root = Path(sys.argv[1])
|
|
fixture_dir = Path(sys.argv[2])
|
|
fixture_ini = Path(sys.argv[3])
|
|
run_dir = Path(sys.argv[4])
|
|
|
|
def emit(name, value):
|
|
print(f"{name}={value}")
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise RuntimeError(message)
|
|
|
|
interp_return = linuxcnc_root / "src/emc/nml_intf/interp_return.hh"
|
|
match = re.search(
|
|
r"INTERP_EXECUTE_FINISH\s*=\s*([0-9]+)",
|
|
interp_return.read_text(encoding="utf-8"),
|
|
)
|
|
require(match, "missing_INTERP_EXECUTE_FINISH_source_value")
|
|
execute_finish = int(match.group(1))
|
|
emit("python_remap_lifecycle_execute_finish_source_value", execute_finish)
|
|
|
|
stub_dir = Path(tempfile.mkdtemp(prefix="python-remap-interpreter-", dir=run_dir))
|
|
(stub_dir / "interpreter.py").write_text(
|
|
f"INTERP_EXECUTE_FINISH = {execute_finish}\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
parser = configparser.ConfigParser(strict=False)
|
|
parser.optionxform = str
|
|
parser.read(fixture_ini)
|
|
require(parser.has_section("PYTHON"), "missing_PYTHON_section")
|
|
path_prepend = parser.get("PYTHON", "PATH_PREPEND", fallback="")
|
|
toplevel = parser.get("PYTHON", "TOPLEVEL", fallback="")
|
|
require(path_prepend == "python", f"PATH_PREPEND_drift:{path_prepend}")
|
|
require(toplevel == "python/toplevel.py", f"TOPLEVEL_drift:{toplevel}")
|
|
emit("python_remap_lifecycle_ini_path_prepend", path_prepend)
|
|
emit("python_remap_lifecycle_ini_toplevel", toplevel)
|
|
|
|
sys.path.insert(0, str(stub_dir))
|
|
sys.path.insert(0, str(fixture_dir / path_prepend))
|
|
sys.path.insert(0, str(linuxcnc_root / "lib/python"))
|
|
|
|
import interpreter # noqa: E402
|
|
require(interpreter.INTERP_EXECUTE_FINISH == execute_finish, "interpreter_sentinel_drift")
|
|
emit("python_remap_lifecycle_interpreter_sentinel_ok", 1)
|
|
|
|
toplevel_module = importlib.import_module(Path(toplevel).stem)
|
|
emit("python_remap_lifecycle_toplevel_imported", int(toplevel_module.__name__ == "toplevel"))
|
|
remap_module = importlib.import_module("remap")
|
|
emit("python_remap_lifecycle_remap_imported", int(remap_module.__name__ == "remap"))
|
|
require(hasattr(remap_module, "queuebuster"), "missing_queuebuster_callable")
|
|
require(callable(remap_module.queuebuster), "queuebuster_not_callable")
|
|
emit("python_remap_lifecycle_callable_lookup_ok", 1)
|
|
|
|
result = remap_module.queuebuster(object())
|
|
require(inspect.isgenerator(result), "queuebuster_not_generator")
|
|
emit("python_remap_lifecycle_generator_returned", 1)
|
|
first_yield = next(result)
|
|
require(first_yield == execute_finish, f"queuebuster_yield_drift:{first_yield}")
|
|
emit("python_remap_lifecycle_generator_first_yield", first_yield)
|
|
try:
|
|
next(result)
|
|
raise RuntimeError("queuebuster_generator_did_not_finish")
|
|
except StopIteration:
|
|
emit("python_remap_lifecycle_generator_finish_ok", 1)
|
|
|
|
emit("python_remap_runtime_lifecycle_probe_ok", 1)
|
|
PY
|
|
then
|
|
cat "$PYTHON_RUNTIME_STDOUT"
|
|
print_kv python_remap_runtime_probe_status "runtime_lifecycle_probe_passed"
|
|
print_kv python_remap_runtime_probe_note "linuxcnc_stop_lookahead_python_lifecycle_probe_passed_without_promotion"
|
|
exit 0
|
|
fi
|
|
|
|
cat "$PYTHON_RUNTIME_STDOUT" || true
|
|
print_kv python_remap_runtime_probe_status "runtime_lifecycle_probe_failed"
|
|
print_kv python_remap_runtime_probe_note "linuxcnc_stop_lookahead_python_lifecycle_probe_failed"
|
|
exit 1
|