mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (CI): merge Arch WSL runner and prefer it over hosted runners
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
name: Configure Test runner
|
||||
description: Select the installed compiler and graphics stack without modifying the host
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_ENVIRONMENT" = "self-hosted" ]; then
|
||||
for tool in clang clang++ cmake ninja ccache; do command -v "$tool"; done
|
||||
test -r /usr/share/vulkan/icd.d/dzn_icd.json
|
||||
test -e /dev/dxg
|
||||
{
|
||||
echo 'MOBILEGL_CI_CC=clang'
|
||||
echo 'MOBILEGL_CI_CXX=clang++'
|
||||
echo 'MOBILEGL_CI_LLD=ld.lld'
|
||||
echo 'MOBILEGL_CI_SOFTWARE_RENDERING=OFF'
|
||||
echo 'VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/dzn_icd.json'
|
||||
echo 'VK_DRIVER_FILES=/usr/share/vulkan/icd.d/dzn_icd.json'
|
||||
echo 'EGL_PLATFORM=surfaceless'
|
||||
echo 'LIBGL_ALWAYS_SOFTWARE=0'
|
||||
echo 'GALLIUM_DRIVER=d3d12'
|
||||
echo 'MESA_LOADER_DRIVER_OVERRIDE=d3d12'
|
||||
} >> "$GITHUB_ENV"
|
||||
else
|
||||
{
|
||||
echo 'MOBILEGL_CI_CC=clang-20'
|
||||
echo 'MOBILEGL_CI_CXX=clang++-20'
|
||||
echo 'MOBILEGL_CI_LLD=ld.lld-20'
|
||||
echo 'MOBILEGL_CI_SOFTWARE_RENDERING=ON'
|
||||
echo 'VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json'
|
||||
echo 'VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json'
|
||||
} >> "$GITHUB_ENV"
|
||||
fi
|
||||
+260
-264
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,138 @@
|
||||
# MiniPC-8845 Arch WSL GPU Runner
|
||||
|
||||
Configuration recorded on 2026-10-05.
|
||||
|
||||
## Host
|
||||
|
||||
| Component | Configuration |
|
||||
| --- | --- |
|
||||
| Hostname | `MiniPC-8845` |
|
||||
| Windows build | `10.0.26200.9445` |
|
||||
| GPU | AMD Radeon 780M Graphics |
|
||||
| Windows GPU driver | `32.0.12033.1030` |
|
||||
| WSL version | `2.7.3.0` |
|
||||
| Distribution | `archlinux`, Arch Linux on WSL 2 |
|
||||
| Linux kernel | `6.6.114.1-microsoft-standard-WSL2` |
|
||||
| Init system | systemd |
|
||||
| WSL networking | Mirrored |
|
||||
| GPU access | `/dev/dxg` and `/usr/lib/wsl/lib` |
|
||||
|
||||
## GitHub Actions Runner
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Repository | `MobileGL-Dev/MobileGL` |
|
||||
| Runner name | `minipc-8845-arch-wsl-gpu` |
|
||||
| Runner version | `2.337.0`, automatic updates enabled |
|
||||
| Labels | `self-hosted`, `Linux`, `X64`, `arch`, `wsl`, `gpu`, `gles`, `vulkan`, `d3d12` |
|
||||
| Concurrent jobs | 1 |
|
||||
| Linux account | `github-runner`, UID 1001, no sudo configuration |
|
||||
| Installation directory | `/opt/actions-runner/mobilegl` |
|
||||
| Work directory | `/opt/actions-runner/mobilegl/_work` |
|
||||
| Environment file | `/etc/mobilegl-runner/gpu.env` |
|
||||
|
||||
Workflow runner selector:
|
||||
|
||||
```yaml
|
||||
runs-on: [self-hosted, Linux, X64, arch, wsl, gpu, gles, vulkan]
|
||||
```
|
||||
|
||||
## Graphics
|
||||
|
||||
| Component | Configuration |
|
||||
| --- | --- |
|
||||
| Mesa | `26.2.1-arch1.1` |
|
||||
| OpenGL / GLES driver | Mesa D3D12 |
|
||||
| Renderer | `D3D12 (AMD Radeon 780M Graphics)` |
|
||||
| EGL | 1.5 |
|
||||
| OpenGL | 4.6 |
|
||||
| OpenGL ES | 3.1 |
|
||||
| Vulkan driver | Mesa Dozen, translating Vulkan to D3D12 |
|
||||
| Vulkan device API | 1.2.354 |
|
||||
| Vulkan ICD | `/usr/share/vulkan/icd.d/dzn_icd.json` |
|
||||
| Default EGL platform | Surfaceless |
|
||||
|
||||
Dozen reports a non-conformant Vulkan implementation. The exposed capabilities
|
||||
are GLES 3.1 and Vulkan 1.2; GLES 3.2 and full Vulkan conformance are not available.
|
||||
|
||||
The runner service selects the Radeon GPU with these environment variables:
|
||||
|
||||
```sh
|
||||
DISPLAY=:0
|
||||
WAYLAND_DISPLAY=wayland-0
|
||||
XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir
|
||||
LD_LIBRARY_PATH=/usr/lib/wsl/lib
|
||||
MESA_LOADER_DRIVER_OVERRIDE=d3d12
|
||||
GALLIUM_DRIVER=d3d12
|
||||
MESA_D3D12_DEFAULT_ADAPTER_NAME="AMD Radeon 780M Graphics"
|
||||
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/dzn_icd.json
|
||||
EGL_PLATFORM=surfaceless
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
```
|
||||
|
||||
The installed GPU probe is `/usr/local/libexec/mobilegl-runner/gpu-smoke`.
|
||||
Its GLES source is `/usr/local/libexec/mobilegl-runner/gles-smoke.c`, and the
|
||||
recorded verification log is `/var/log/mobilegl-runner/gpu-smoke.log`.
|
||||
|
||||
## Build Tools
|
||||
|
||||
Installed tools include GCC, Clang 22, CMake, Ninja, ccache, LLD, libc++, Git LFS,
|
||||
GitHub CLI, and EGL/GLES/Vulkan development headers.
|
||||
|
||||
## Service and Startup
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| systemd unit | `actions.runner.MobileGL-Dev-MobileGL.minipc-8845-arch-wsl-gpu.service` |
|
||||
| Service drop-in | `/etc/systemd/system/actions.runner.MobileGL-Dev-MobileGL.minipc-8845-arch-wsl-gpu.service.d/gpu.conf` |
|
||||
| Service startup | Enabled at WSL systemd boot |
|
||||
| Restart policy | `Restart=always`, `RestartSec=15` |
|
||||
| Startup checks | GPU device access and WSLg X11/Wayland sockets |
|
||||
| Windows scheduled task | `MobileGL Arch WSL Runner` |
|
||||
| Task trigger | Windows login by `Swung0x48` |
|
||||
| Task behavior | Starts WSL in a hidden window and keeps it running |
|
||||
| Task execution limit | Unlimited |
|
||||
| Task retry policy | Every minute, up to 999 retries |
|
||||
|
||||
Automatic startup requires the Windows user to log in. The runner cannot execute
|
||||
jobs while the host is asleep.
|
||||
|
||||
## Network
|
||||
|
||||
The runner uses the Windows Clash proxy through WSL mirrored networking:
|
||||
|
||||
```sh
|
||||
http_proxy=http://127.0.0.1:7897
|
||||
https_proxy=http://127.0.0.1:7897
|
||||
no_proxy=localhost,127.0.0.1,::1
|
||||
```
|
||||
|
||||
Clash must be running for the runner's configured GitHub connection to work.
|
||||
|
||||
## Main Test workflow
|
||||
|
||||
`test.yml` uses a small GitHub-hosted `select-runner` job to query this repository's
|
||||
runner inventory. Every subsequent job uses its single decision, so artifacts built
|
||||
against Arch's compiler/libc/Mesa cannot accidentally be consumed by Ubuntu jobs.
|
||||
An online runner is preferred even when busy. Offline, unregistered, or mismatched
|
||||
labels select `ubuntu-latest` for the whole run. Test failures do not cause fallback.
|
||||
|
||||
Configure repository Actions secret `RUNNER_STATUS_TOKEN` with a fine-grained PAT
|
||||
restricted to this repository and **Administration: Read-only**. The ordinary
|
||||
`GITHUB_TOKEN` cannot generally list repository runners. A missing/expired token or
|
||||
API failure stops selection with an error: unknown availability is not treated as
|
||||
an offline machine. The token is passed only to the GitHub-hosted selector.
|
||||
|
||||
Availability is sampled at workflow start. If the machine disconnects after
|
||||
selection, GitHub queues its remaining jobs; rerunning the workflow while it is
|
||||
offline chooses Ubuntu. GitHub Actions does not migrate already scheduled jobs
|
||||
between runner types. Keeping one platform per run avoids incompatible artifacts.
|
||||
|
||||
The local composite setup action uses Arch's installed Clang/CMake and Dozen ICD.
|
||||
Ubuntu package installation, swap provisioning, SDK download, and privileged core
|
||||
pattern changes are skipped on this no-sudo host. Compiler caches are separated by
|
||||
runner pool. Trace tests disable forced software-rendering environment overrides
|
||||
on this machine; the Ubuntu fallback retains its existing Mesa software settings.
|
||||
Hardware capability differences remain visible as test results, not fallback triggers.
|
||||
The dedicated `wsl-gpu-smoke.yml` workflow has been removed; the host's installed
|
||||
probe remains available to its operator.
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Choose one execution platform for the entire Test artifact dependency graph."""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
NAME = "minipc-8845-arch-wsl-gpu"
|
||||
LABELS = ["self-hosted", "Linux", "X64", "arch", "wsl", "gpu", "gles", "vulkan"]
|
||||
|
||||
|
||||
def choose(runners):
|
||||
for runner in runners:
|
||||
labels = {label["name"].lower() for label in runner.get("labels", [])}
|
||||
if runner.get("name") == NAME and set(map(str.lower, LABELS)) <= labels:
|
||||
if runner.get("status") == "online":
|
||||
# Busy is still available: queue behind its current job.
|
||||
return LABELS, "arch-wsl", "preferred runner online (busy=%s)" % runner.get("busy", False)
|
||||
return ["ubuntu-latest"], "github", "preferred runner offline, absent, or missing required labels"
|
||||
|
||||
|
||||
def runner_inventory(repository, token):
|
||||
if not token:
|
||||
raise ValueError("Set RUNNER_STATUS_TOKEN with repository Administration: read; runner availability is unknown")
|
||||
runners = []
|
||||
page = 1
|
||||
while True:
|
||||
url = f"https://api.github.com/repos/{repository}/actions/runners?per_page=100&page={page}"
|
||||
request = urllib.request.Request(url, headers={
|
||||
"Authorization": "Bearer " + token,
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"User-Agent": "MobileGL-test-runner-selector",
|
||||
})
|
||||
with urllib.request.urlopen(request, timeout=20) as response:
|
||||
data = json.load(response)
|
||||
batch = data["runners"]
|
||||
runners.extend(batch)
|
||||
if len(batch) < 100:
|
||||
return runners
|
||||
page += 1
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
runners = runner_inventory(os.environ["GITHUB_REPOSITORY"], os.environ.get("RUNNER_STATUS_TOKEN", ""))
|
||||
labels, pool, reason = choose(runners)
|
||||
except (ValueError, KeyError, OSError, urllib.error.URLError) as error:
|
||||
# An API/auth error is not evidence that the machine is offline. Do not
|
||||
# silently send the whole matrix to GitHub when the status cannot be read.
|
||||
print(f"::error::Cannot determine runner availability: {error}", file=sys.stderr)
|
||||
return 1
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
|
||||
output.write("runner=" + json.dumps(labels) + "\npool=" + pool + "\n")
|
||||
print(f"Test runner: {pool}: {reason}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -44,7 +44,7 @@ def run_arm(build, out, case, forward):
|
||||
if Path('/usr/share/glvnd/egl_vendor.d/50_mesa.json').exists():
|
||||
env['__EGL_VENDOR_LIBRARY_FILENAMES'] = '/usr/share/glvnd/egl_vendor.d/50_mesa.json'
|
||||
if Path('/usr/share/vulkan/icd.d/lvp_icd.json').exists():
|
||||
env['VK_ICD_FILENAMES'] = '/usr/share/vulkan/icd.d/lvp_icd.json'
|
||||
env.setdefault('VK_ICD_FILENAMES', '/usr/share/vulkan/icd.d/lvp_icd.json')
|
||||
peer_env = dict(env, MOBILEGL_IPC_ROLE='server', MOBILEGL_IPC_DIAL='no',
|
||||
MOBILEGL_IPC_LOG_FORWARD=str(int(forward)), MOBILEGL_LOG_FILE_PATH=str(out / 'peer.log'))
|
||||
for name in ('MOBILEGL_TRANSPORT', 'MOBILEGL_IPC_SERVER_PATH', 'MOBILEGL_IPC_RING_MB', 'MOBILEGL_IPC_STAGE_MB'):
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import urllib.error
|
||||
|
||||
spec = importlib.util.spec_from_file_location("selector", Path(__file__).with_name("select_test_runner.py"))
|
||||
selector = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(selector)
|
||||
|
||||
|
||||
def runner(status="online", busy=False):
|
||||
return {"name": selector.NAME, "status": status, "busy": busy,
|
||||
"labels": [{"name": label} for label in selector.LABELS]}
|
||||
|
||||
|
||||
class SelectRunnerTest(unittest.TestCase):
|
||||
def test_online_busy_and_idle_both_use_preferred(self):
|
||||
for busy in (False, True):
|
||||
labels, pool, _ = selector.choose([runner(busy=busy)])
|
||||
self.assertEqual(labels, selector.LABELS)
|
||||
self.assertEqual(pool, "arch-wsl")
|
||||
|
||||
def test_offline_or_absent_falls_back(self):
|
||||
for rows in ([], [runner("offline")]):
|
||||
self.assertEqual(selector.choose(rows)[:2], (["ubuntu-latest"], "github"))
|
||||
|
||||
def test_wrong_runner_or_missing_labels_cannot_queue_forever(self):
|
||||
wrong = runner()
|
||||
wrong["name"] = "another-machine"
|
||||
missing = runner()
|
||||
missing["labels"] = missing["labels"][:-1]
|
||||
for rows in ([wrong], [missing]):
|
||||
self.assertEqual(selector.choose(rows)[1], "github")
|
||||
|
||||
def test_inventory_reads_later_pages(self):
|
||||
pages = [io.StringIO(json.dumps({"runners": [{}] * 100})),
|
||||
io.StringIO(json.dumps({"runners": [runner()]}))]
|
||||
with patch.object(selector.urllib.request, "urlopen", side_effect=pages) as request:
|
||||
rows = selector.runner_inventory("owner/repo", "not-a-real-token")
|
||||
self.assertEqual(len(rows), 101)
|
||||
self.assertTrue(request.call_args.args[0].full_url.endswith("page=2"))
|
||||
|
||||
def test_auth_failure_does_not_masquerade_as_offline(self):
|
||||
for error in (ValueError("missing token"), urllib.error.HTTPError("url", 403, "Forbidden", {}, None)):
|
||||
with patch.dict(os.environ, {"GITHUB_REPOSITORY": "owner/repo"}), \
|
||||
patch.object(selector, "runner_inventory", side_effect=error), \
|
||||
contextlib.redirect_stderr(io.StringIO()):
|
||||
self.assertEqual(selector.main(), 1)
|
||||
|
||||
def test_outputs_are_valid_runs_on_json(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = Path(root) / "outputs"
|
||||
with patch.dict(os.environ, {"GITHUB_REPOSITORY": "owner/repo", "GITHUB_OUTPUT": str(path)}), \
|
||||
patch.object(selector, "runner_inventory", return_value=[runner()]), \
|
||||
contextlib.redirect_stdout(io.StringIO()):
|
||||
self.assertEqual(selector.main(), 0)
|
||||
values = dict(line.split("=", 1) for line in path.read_text().splitlines())
|
||||
self.assertEqual(json.loads(values["runner"]), selector.LABELS)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -5,6 +5,8 @@ if(NOT UNIX)
|
||||
return()
|
||||
endif()
|
||||
|
||||
option(MOBILEGL_TRACE_SOFTWARE_RENDERING "Force Mesa software rendering for trace tests" ON)
|
||||
|
||||
set(MOBILEGL_TRACE_ROOT "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(MOBILEGL_TRACE_SHARED_CPP_DIR "${CMAKE_SOURCE_DIR}/android-plugin/app/src/trace/cpp")
|
||||
set(APITRACE_ROOT "${CMAKE_SOURCE_DIR}/3rdparty/apitrace")
|
||||
@@ -445,15 +447,16 @@ function(add_trace_replay_test CASE_NAME BACKEND)
|
||||
set(trace_case_labels "retrace\;retrace-${TRACE_CASE_VARIANT}")
|
||||
string(TOLOWER "${trace_case_labels}" trace_case_labels)
|
||||
endif()
|
||||
if(BACKEND STREQUAL "DirectGLES")
|
||||
set_tests_properties(${trace_case_name} PROPERTIES
|
||||
ENVIRONMENT "EGL_PLATFORM=surfaceless;LIBGL_ALWAYS_SOFTWARE=1;MESA_GL_VERSION_OVERRIDE=3.3;MESA_GLSL_VERSION_OVERRIDE=330"
|
||||
LABELS "${trace_case_labels}")
|
||||
else()
|
||||
set_tests_properties(${trace_case_name} PROPERTIES
|
||||
ENVIRONMENT "LIBGL_ALWAYS_SOFTWARE=1;MESA_GL_VERSION_OVERRIDE=3.3;MESA_GLSL_VERSION_OVERRIDE=330"
|
||||
LABELS "${trace_case_labels}")
|
||||
set(trace_case_environment "")
|
||||
if(MOBILEGL_TRACE_SOFTWARE_RENDERING)
|
||||
list(APPEND trace_case_environment LIBGL_ALWAYS_SOFTWARE=1
|
||||
MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330)
|
||||
endif()
|
||||
if(BACKEND STREQUAL "DirectGLES")
|
||||
list(APPEND trace_case_environment EGL_PLATFORM=surfaceless)
|
||||
endif()
|
||||
set_tests_properties(${trace_case_name} PROPERTIES
|
||||
ENVIRONMENT "${trace_case_environment}" LABELS "${trace_case_labels}")
|
||||
if(TRACE_CASE_IPC_CONTROL)
|
||||
set_tests_properties(${trace_case_name} PROPERTIES RESOURCE_LOCK mobilegl-tcp-trace)
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user