From c83bc111b47d81180e26220b08b883a758a3615c Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:19:19 +0800 Subject: [PATCH] CI: add rip-headless job testing --disable-gui builds keeps working. The new rip-headless job configures with --disable-gui --disable-manpages --disable-build-documentation using a minimal dependency set without X11, Tk or GTK development packages, so a regression that reintroduces a GUI dependency into the headless build fails at configure or build time instead of being masked by full build-deps. After the build it asserts that GUI artifacts (axis, touchy, halshow, pyvcp, ...) are absent and core headless artifacts (linuxcncrsh, halcmd, halui, ...) exist, then runs the full test suite. Also included, all found by running the full suite on a headless build: - pyvcp and gladevcp were still built with --disable-gui (#4364 guarded only the install rules); guard USER_COMP_PY in hal/user_comps/Submakefile. - preview_helpers.py moved from the GUI-gated gremlin directory to lib/python: it is a pure-Python helper needed by the GUI-free tests/interp_initcode test. - Tests needing Tcl/GUI bits a headless build does not provide (pyvcp, twopass, twopass-personality, tclsh-extensions, tooledit, ui-smoke) get skip scripts, the mechanism the ui-smoke tests already use. The ui-smoke skip helper now takes the GUI binary under test as an argument. --- .github/scripts/install-deps-headless.sh | 41 +++++++++++++++ .github/workflows/ci.yml | 52 +++++++++++++++++++ .gitignore | 1 - .../gremlin => lib/python}/preview_helpers.py | 0 src/emc/usr_intf/gremlin/Submakefile | 2 +- src/hal/user_comps/Submakefile | 5 +- tests/pyvcp/skip | 16 ++++++ tests/tclsh-extensions/skip | 19 +++++++ tests/tooledit/skip | 27 ++++++++++ tests/twopass-personality/skip | 18 +++++++ tests/twopass/skip | 18 +++++++ tests/ui-smoke/_lib/skip-if-missing.sh | 16 ++++-- tests/ui-smoke/axis/skip | 2 +- tests/ui-smoke/gmoccapy-quit/skip | 2 +- tests/ui-smoke/gmoccapy/skip | 2 +- tests/ui-smoke/qtdragon-quit/skip | 2 +- tests/ui-smoke/qtdragon/skip | 2 +- tests/ui-smoke/touchy-quit/skip | 2 +- tests/ui-smoke/touchy/skip | 2 +- 19 files changed, 215 insertions(+), 14 deletions(-) create mode 100755 .github/scripts/install-deps-headless.sh rename {src/emc/usr_intf/gremlin => lib/python}/preview_helpers.py (100%) create mode 100755 tests/pyvcp/skip create mode 100755 tests/tclsh-extensions/skip create mode 100755 tests/tooledit/skip create mode 100755 tests/twopass-personality/skip create mode 100755 tests/twopass/skip diff --git a/.github/scripts/install-deps-headless.sh b/.github/scripts/install-deps-headless.sh new file mode 100755 index 00000000000..f2cdb599249 --- /dev/null +++ b/.github/scripts/install-deps-headless.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +set -eu #Needed so CI fails when anything is wrong +set -x + +# Minimal dependency set for a --disable-gui build: no X11, Tk, GTK or +# documentation tooling. If a future change makes the headless build +# silently depend on a GUI library again, this job fails at configure +# or build time instead of papering over it with full build-deps. + +.github/scripts/use-main-ubuntu-mirror.sh + +sudo apt-get --quiet update +sudo apt-get install --yes --no-install-recommends \ + build-essential \ + autoconf \ + automake \ + pkg-config \ + python3 \ + python3-dev \ + yapps2 \ + intltool \ + gettext \ + libreadline-dev \ + libtirpc-dev \ + libudev-dev \ + libglib2.0-dev \ + libmodbus-dev \ + libusb-1.0-0-dev \ + libboost-python-dev \ + libfmt-dev \ + python3-pybind11 \ + python3-numpy \ + libcap-dev \ + libedit-dev \ + procps \ + psmisc \ + sysvinit-utils \ + util-linux \ + kmod \ + netcat-openbsd diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 518315a9d67..72b2bdca727 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,58 @@ jobs: run: | .github/scripts/verify-clean-repo.sh + rip-headless: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: | + echo Number of CPUs: $(nproc) + echo "$GITHUB_CONTEXT" + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: false + fetch-depth: 1 + - name: Install minimal headless dependencies + run: | + set -x + .github/scripts/install-deps-headless.sh + - name: Build RIP headless + run: | + set -x + .github/scripts/build-rip.sh --with-realtime=uspace --disable-gui --disable-manpages --disable-build-documentation + - name: Verify GUI artifacts are absent and headless artifacts exist + run: | + set -x + for f in bin/axis bin/axis-remote bin/touchy bin/gscreen bin/gmoccapy \ + bin/halshow bin/pyvcp bin/gladevcp bin/stepconf bin/pncconf \ + bin/image-to-gcode tcl/linuxcnc.so tcl/hal.so; do + if test -e "$f"; then + echo "error: GUI artifact built despite --disable-gui: $f" + exit 1 + fi + done + for f in scripts/linuxcnc scripts/halrun bin/linuxcncrsh bin/linuxcncsvr \ + bin/halcmd bin/halui bin/mdi bin/linuxcnctop; do + if test ! -x "$f"; then + echo "error: missing headless artifact: $f" + exit 1 + fi + done + - name: Verify no untracked or modified files after build + run: | + .github/scripts/verify-clean-repo.sh + - name: Test + run: | + set -x + scripts/rip-environment runtests -p ./tests + - name: Verify no untracked or modified files after test + run: | + .github/scripts/verify-clean-repo.sh + rip-rtai: runs-on: ubuntu-24.04 timeout-minutes: 45 diff --git a/.gitignore b/.gitignore index 31439a43c6d..4e3ccbcb4c8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ src/modules.order /configs/*/emc.nml !/configs/common/emc.nml lib/python/gremlin.py -lib/python/preview_helpers.py lib/python/qt5_graphics.py lib/python/touchy configs/sim/gmoccapy/*.pref diff --git a/src/emc/usr_intf/gremlin/preview_helpers.py b/lib/python/preview_helpers.py similarity index 100% rename from src/emc/usr_intf/gremlin/preview_helpers.py rename to lib/python/preview_helpers.py diff --git a/src/emc/usr_intf/gremlin/Submakefile b/src/emc/usr_intf/gremlin/Submakefile index 541e0638e0c..4315900a2ec 100644 --- a/src/emc/usr_intf/gremlin/Submakefile +++ b/src/emc/usr_intf/gremlin/Submakefile @@ -1,4 +1,4 @@ -PYTARGETS += ../bin/gremlin ../lib/python/gremlin.py ../lib/python/qt5_graphics.py ../lib/python/preview_helpers.py +PYTARGETS += ../bin/gremlin ../lib/python/gremlin.py ../lib/python/qt5_graphics.py PYI18NSRCS += emc/usr_intf/gremlin/gremlin.py ../lib/python/%: emc/usr_intf/gremlin/% diff --git a/src/hal/user_comps/Submakefile b/src/hal/user_comps/Submakefile index df838005817..c2dd24af82c 100644 --- a/src/hal/user_comps/Submakefile +++ b/src/hal/user_comps/Submakefile @@ -1,4 +1,7 @@ -USER_COMP_PY = pyvcp hal_input gladevcp scorbot-er-3 mitsub_vfd pmx485 sim-torch z_level_compensation mqtt-publisher hal_bridge mtconnect-agent +USER_COMP_PY = hal_input scorbot-er-3 mitsub_vfd pmx485 sim-torch z_level_compensation mqtt-publisher hal_bridge mtconnect-agent +ifeq ($(BUILD_GUI),yes) +USER_COMP_PY += pyvcp gladevcp +endif USER_COMPS := $(sort $(wildcard hal/user_comps/*.comp)) USER_COMP_BINS := $(patsubst hal/user_comps/%.comp, ../bin/%, $(USER_COMPS)) diff --git a/tests/pyvcp/skip b/tests/pyvcp/skip new file mode 100755 index 00000000000..267bea59433 --- /dev/null +++ b/tests/pyvcp/skip @@ -0,0 +1,16 @@ +#!/bin/bash +# pyvcp is only built in GUI builds (BUILD_GUI); skip in headless builds. +# Also skip without xvfb-run (minimal local environments), like ui-smoke. +set -u + +if ! command -v pyvcp >/dev/null 2>&1; then + echo "skip: pyvcp not built (headless build)" >&2 + exit 1 +fi + +if ! command -v xvfb-run >/dev/null 2>&1; then + echo "skip: xvfb-run not installed" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tclsh-extensions/skip b/tests/tclsh-extensions/skip new file mode 100755 index 00000000000..9dbfdc9945d --- /dev/null +++ b/tests/tclsh-extensions/skip @@ -0,0 +1,19 @@ +#!/bin/bash +# This test exercises the Tcl HAL extensions (tcl/hal.so, +# tcl/linuxcnc.so), which are only built in GUI builds (BUILD_GUI); +# skip in headless builds. The tcl dir is taken from TCLLIBPATH +# (exported by rip-environment). +set -u + +if ! command -v tclsh >/dev/null 2>&1; then + echo "skip: tclsh not installed" >&2 + exit 1 +fi + +read -r tcldir _ <<< "${TCLLIBPATH:-}" +if ! [ -f "${tcldir:-/nonexistent}/linuxcnc.so" ]; then + echo "skip: Tcl HAL extensions not built (headless build)" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tooledit/skip b/tests/tooledit/skip new file mode 100755 index 00000000000..dc9432e9e6b --- /dev/null +++ b/tests/tooledit/skip @@ -0,0 +1,27 @@ +#!/bin/bash +# tooledit is a Tk/BWidget application; headless builds (--disable-gui) +# do not build or run any of the Tk tooling. Skip when the required +# Tcl environment is unavailable. BWidget pulls in Tk on load, which +# needs a display, so probe under xvfb-run; the explicit exit is needed +# because 'package require' errors do not reliably set tclsh's exit +# code when reading commands from stdin. Probe on server number 95: +# xvfb-run's default is 99, and test.sh's plain xvfb-run would race a +# lingering Xvfb on the same display. +set -u + +if ! command -v xvfb-run >/dev/null 2>&1; then + echo "skip: xvfb-run not installed" >&2 + exit 1 +fi + +if ! command -v tclsh >/dev/null 2>&1; then + echo "skip: tclsh not installed" >&2 + exit 1 +fi + +if ! xvfb-run -a --server-num=95 tclsh <<< 'exit [catch {package require BWidget}]'; then + echo "skip: Tk/BWidget not available (headless build)" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/twopass-personality/skip b/tests/twopass-personality/skip new file mode 100755 index 00000000000..b05d6ab784b --- /dev/null +++ b/tests/twopass-personality/skip @@ -0,0 +1,18 @@ +#!/bin/bash +# haltcl needs the Tcl HAL extension (tcl/hal.so), which is only built +# in GUI builds (BUILD_GUI); skip in headless builds. The tcl dir is +# taken from TCLLIBPATH (exported by rip-environment). +set -u + +if ! command -v tclsh >/dev/null 2>&1; then + echo "skip: tclsh not installed" >&2 + exit 1 +fi + +read -r tcldir _ <<< "${TCLLIBPATH:-}" +if ! [ -f "${tcldir:-/nonexistent}/hal.so" ]; then + echo "skip: Tcl HAL extension not built (headless build)" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/twopass/skip b/tests/twopass/skip new file mode 100755 index 00000000000..b05d6ab784b --- /dev/null +++ b/tests/twopass/skip @@ -0,0 +1,18 @@ +#!/bin/bash +# haltcl needs the Tcl HAL extension (tcl/hal.so), which is only built +# in GUI builds (BUILD_GUI); skip in headless builds. The tcl dir is +# taken from TCLLIBPATH (exported by rip-environment). +set -u + +if ! command -v tclsh >/dev/null 2>&1; then + echo "skip: tclsh not installed" >&2 + exit 1 +fi + +read -r tcldir _ <<< "${TCLLIBPATH:-}" +if ! [ -f "${tcldir:-/nonexistent}/hal.so" ]; then + echo "skip: Tcl HAL extension not built (headless build)" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/ui-smoke/_lib/skip-if-missing.sh b/tests/ui-smoke/_lib/skip-if-missing.sh index 77a0155efcc..ca4e839a33b 100755 --- a/tests/ui-smoke/_lib/skip-if-missing.sh +++ b/tests/ui-smoke/_lib/skip-if-missing.sh @@ -3,10 +3,13 @@ # runtests semantics: a `skip` script that returns non-zero causes the # test to be skipped. Per-test skip files invoke this. # -# We only skip on xvfb-run absence (rare local dev env). Python / -# typelib deps are declared in debian/control under !nocheck so CI -# always has them; missing deps should fail the test loudly rather -# than silently skip (BsAtHome / hdiethelm review, PR #3999). +# We only skip on xvfb-run absence (rare local dev env) or when the +# GUI under test was not built (--disable-gui headless builds). +# Python / typelib deps are declared in debian/control under !nocheck +# so CI always has them; missing deps should fail the test loudly +# rather than silently skip (BsAtHome / hdiethelm review, PR #3999). +# +# Usage: skip-if-missing.sh [gui-binary] set -u if ! command -v xvfb-run >/dev/null 2>&1; then @@ -14,4 +17,9 @@ if ! command -v xvfb-run >/dev/null 2>&1; then exit 1 fi +if [ $# -ge 1 ] && ! command -v "$1" >/dev/null 2>&1; then + echo "skip: $1 not built (headless build)" >&2 + exit 1 +fi + exit 0 diff --git a/tests/ui-smoke/axis/skip b/tests/ui-smoke/axis/skip index c1c260edf05..50d7bb37884 100755 --- a/tests/ui-smoke/axis/skip +++ b/tests/ui-smoke/axis/skip @@ -1,2 +1,2 @@ #!/bin/bash -exec "$(dirname "$0")/../_lib/skip-if-missing.sh" +exec "$(dirname "$0")/../_lib/skip-if-missing.sh" axis diff --git a/tests/ui-smoke/gmoccapy-quit/skip b/tests/ui-smoke/gmoccapy-quit/skip index c1c260edf05..bdf693e3dcd 100755 --- a/tests/ui-smoke/gmoccapy-quit/skip +++ b/tests/ui-smoke/gmoccapy-quit/skip @@ -1,2 +1,2 @@ #!/bin/bash -exec "$(dirname "$0")/../_lib/skip-if-missing.sh" +exec "$(dirname "$0")/../_lib/skip-if-missing.sh" gmoccapy diff --git a/tests/ui-smoke/gmoccapy/skip b/tests/ui-smoke/gmoccapy/skip index c1c260edf05..bdf693e3dcd 100755 --- a/tests/ui-smoke/gmoccapy/skip +++ b/tests/ui-smoke/gmoccapy/skip @@ -1,2 +1,2 @@ #!/bin/bash -exec "$(dirname "$0")/../_lib/skip-if-missing.sh" +exec "$(dirname "$0")/../_lib/skip-if-missing.sh" gmoccapy diff --git a/tests/ui-smoke/qtdragon-quit/skip b/tests/ui-smoke/qtdragon-quit/skip index c1c260edf05..684a0f9e437 100755 --- a/tests/ui-smoke/qtdragon-quit/skip +++ b/tests/ui-smoke/qtdragon-quit/skip @@ -1,2 +1,2 @@ #!/bin/bash -exec "$(dirname "$0")/../_lib/skip-if-missing.sh" +exec "$(dirname "$0")/../_lib/skip-if-missing.sh" qtvcp diff --git a/tests/ui-smoke/qtdragon/skip b/tests/ui-smoke/qtdragon/skip index c1c260edf05..684a0f9e437 100755 --- a/tests/ui-smoke/qtdragon/skip +++ b/tests/ui-smoke/qtdragon/skip @@ -1,2 +1,2 @@ #!/bin/bash -exec "$(dirname "$0")/../_lib/skip-if-missing.sh" +exec "$(dirname "$0")/../_lib/skip-if-missing.sh" qtvcp diff --git a/tests/ui-smoke/touchy-quit/skip b/tests/ui-smoke/touchy-quit/skip index c1c260edf05..a4da8fca401 100755 --- a/tests/ui-smoke/touchy-quit/skip +++ b/tests/ui-smoke/touchy-quit/skip @@ -1,2 +1,2 @@ #!/bin/bash -exec "$(dirname "$0")/../_lib/skip-if-missing.sh" +exec "$(dirname "$0")/../_lib/skip-if-missing.sh" touchy diff --git a/tests/ui-smoke/touchy/skip b/tests/ui-smoke/touchy/skip index c1c260edf05..a4da8fca401 100755 --- a/tests/ui-smoke/touchy/skip +++ b/tests/ui-smoke/touchy/skip @@ -1,2 +1,2 @@ #!/bin/bash -exec "$(dirname "$0")/../_lib/skip-if-missing.sh" +exec "$(dirname "$0")/../_lib/skip-if-missing.sh" touchy