feat(build): optional xvfb-run for Linux GUI builds

Add an --xvfb switch to 'build run' and 'job' that wraps the Linux build
command in 'xvfb-run -a sh -c ...', giving GUI toolkits (GTK4/PyGObject)
a headless $DISPLAY. Exported env vars precede xvfb-run so they
propagate into the virtual-display session. No-op (with a notice) for
Windows guests.

The local-ci-build composite action exposes it as the 'xvfb' input
(default false), forwarded as INPUT_XVFB -> --xvfb, mirroring the
use-shared-cache/skip-artifact wiring.

Docs: WORKFLOW-AUTHORING.md parameter table + Linux example note;
LINUX-TEMPLATE-SETUP.md Step 4a (the template ships python3-gi
gir1.2-gtk-4.0 xvfb). Adds build-run unit tests (wrap, plain, Windows
no-op).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 13:38:34 +02:00
parent e91e55f20e
commit 6f51d44f92
6 changed files with 146 additions and 7 deletions
+77
View File
@@ -212,6 +212,83 @@ def test_build_run_linux_clone_url_happy_path(
assert any("/work/build" in c for c in cmds)
def test_build_run_linux_xvfb_wraps_command(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""--xvfb wraps the Linux build in xvfb-run; env exports stay outside."""
_patch(monkeypatch)
result = CliRunner().invoke(
cli,
[
"build", "run",
"--ip-address", "10.0.0.5",
"--guest-os", "linux",
"--clone-url", "https://example/repo.git",
"--build-command", "make all",
"--guest-linux-work-dir", "/work",
"--extra-env", "FOO=bar",
"--xvfb",
"--ssh-key-path", str(tmp_path / "key"),
],
)
assert result.exit_code == 0, result.output
cmds = [c[0] for c in _FakeTransport.instances[0].runs]
# Build wrapped under xvfb-run via sh -c; env export precedes it.
assert any(
"xvfb-run -a sh -c 'cd /work && make all'" in c
and "export FOO=bar" in c
for c in cmds
)
def test_build_run_linux_no_xvfb_runs_plain(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Without --xvfb the build runs directly (no xvfb-run wrapper)."""
_patch(monkeypatch)
result = CliRunner().invoke(
cli,
[
"build", "run",
"--ip-address", "10.0.0.5",
"--guest-os", "linux",
"--clone-url", "https://example/repo.git",
"--build-command", "make all",
"--guest-linux-work-dir", "/work",
"--ssh-key-path", str(tmp_path / "key"),
],
)
assert result.exit_code == 0, result.output
cmds = [c[0] for c in _FakeTransport.instances[0].runs]
assert any("cd /work && make all" in c for c in cmds)
assert not any("xvfb-run" in c for c in cmds)
def test_build_run_xvfb_ignored_on_windows(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""--xvfb is a no-op (with a notice) for Windows guests."""
_patch(monkeypatch)
src = tmp_path / "src"
src.mkdir()
(src / "f.txt").write_text("x")
result = CliRunner().invoke(
cli,
[
"build", "run",
"--ip-address", "10.0.0.9",
"--guest-os", "windows",
"--host-source-dir", str(src),
"--build-command", "echo hi",
"--xvfb",
],
)
assert result.exit_code == 0, result.output
assert "--xvfb ignored for Windows guest" in result.output
cmds = [c[0] for c in _FakeTransport.instances[0].runs]
assert not any("xvfb-run" in c for c in cmds)
def test_build_run_linux_host_source_dir(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None: