the modules carry the organization that owns the protocol #20
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Build every member on BOTH toolchains, and check what a green compile would | |
| # not: that the generator is ours, that the sonames are canonical, that the two | |
| # libraries do not overlap, and that every module produced an interface. | |
| name: ci | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| pull_request: | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Both, because the module wrappers are where the two compilers differ | |
| # most: a 300-entry `using ::name;` export block is exactly the shape | |
| # that finds a disagreement about what may be named in an export. | |
| toolchain: [default, llvm] | |
| name: build (${{ matrix.toolchain }}) | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Installed the way a user does, through xlings, rather than by | |
| # unpacking an mcpp release: these packages depend on the ECOSYSTEM | |
| # (`xim:mesa`'s GBM discovery row, the toolchains), and a pinned mcpp | |
| # tarball carries a frozen snapshot of it. Testing against the current | |
| # ecosystem is the point. | |
| # | |
| # XLINGS_NON_INTERACTIVE is what makes the installer usable here. Without | |
| # it the script takes its `curl | bash` branch — `xlings self install < | |
| # /dev/tty` — and a runner has a /dev/tty that is readable but not | |
| # connected, so it dies with "No such device or address" before anything | |
| # is installed. | |
| # | |
| # XLINGS_VERSION PINS THE INSTALLER. Unpinned, this step installed | |
| # whatever was newest that morning, so a red run could never be told | |
| # apart from an ecosystem change — the one thing a fork's CI exists to | |
| # rule out. The installer reads `${1:-${XLINGS_VERSION:-}}`, so the | |
| # environment form pins it without changing the `curl | bash` shape. | |
| # | |
| # mcpp itself stays UNPINNED on purpose, and that is not an oversight: | |
| # these packages depend on the ECOSYSTEM (`xim:mesa`'s GBM/EGL discovery | |
| # rows, the toolchains), and a pinned mcpp tarball carries a frozen | |
| # snapshot of it. Pinning the tool that installs, floating the ecosystem | |
| # under test, is the split that makes a failure attributable. | |
| - name: Install xlings + mcpp | |
| env: | |
| XLINGS_NON_INTERACTIVE: "1" | |
| XLINGS_VERSION: "2026.8.27.4" | |
| run: | | |
| curl -fsSL https://d2learn.org/xlings-install.sh | bash | |
| echo "$HOME/.xlings/bin" >> "$GITHUB_PATH" | |
| "$HOME/.xlings/bin/xlings" --version | |
| "$HOME/.xlings/bin/xlings" install mcpp | |
| echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH" | |
| - name: Select toolchain | |
| if: matrix.toolchain != 'default' | |
| run: | | |
| mcpp toolchain install ${{ matrix.toolchain }} | |
| mcpp toolchain default ${{ matrix.toolchain }} | |
| mcpp toolchain list | |
| - name: Build the workspace | |
| run: | | |
| mcpp --version | |
| mcpp build --workspace | |
| - name: The macro mappings actually run | |
| run: mcpp test -p mcpp/util | |
| # Artifacts are located by searching the tree rather than by assuming | |
| # `mcpp/<member>/target/...`: where a workspace build puts its output is | |
| # mcpp's business and has moved before. On a miss the step prints what it | |
| # DID find, so the next failure is diagnosable from the log alone. | |
| - name: wayland-scanner is ours, and is 1.26.0 | |
| run: | | |
| scanner=$(find . -name wayland-scanner -type f -perm -u+x | head -1) | |
| if [ -z "$scanner" ]; then | |
| echo "::error::no wayland-scanner binary anywhere in the tree" | |
| find . -path ./upstream -prune -o -name 'target' -type d -print | |
| exit 1 | |
| fi | |
| echo "found: $scanner" | |
| # --version goes to STDERR, so the pipe needs 2>&1 or grep reads nothing | |
| # and the check fails on a scanner that is perfectly fine. | |
| "$scanner" --version 2>&1 | |
| "$scanner" --version 2>&1 | grep -qx 'wayland-scanner 1.26.0' | |
| - name: the generated protocol code came from THIS scanner | |
| run: | | |
| scanner=$(find . -name wayland-scanner -type f -perm -u+x | head -1) | |
| "$scanner" -s public-code upstream/protocol/wayland.xml /tmp/wayland-protocol.c | |
| head -1 /tmp/wayland-protocol.c | |
| grep -q 'Generated by wayland-scanner 1.26.0' /tmp/wayland-protocol.c | |
| - name: both libraries carry the canonical SONAMEs | |
| run: | | |
| for pair in "client:libwayland-client.so.0" "server:libwayland-server.so.0"; do | |
| member=${pair%%:*}; want=${pair#*:} | |
| so=$(find . -name "${want%.0}" -type f | head -1) | |
| test -n "$so" || { echo "$member: no library built"; exit 1; } | |
| got=$(readelf -d "$so" | sed -n 's/.*SONAME.*\[\(.*\)\]/\1/p') | |
| echo "$member -> $got" | |
| test "$got" = "$want" || { echo "expected $want"; exit 1; } | |
| done | |
| - name: the two libraries do not overlap | |
| run: | | |
| # Mesa's libEGL_mesa has DT_NEEDED on both; if one carried the other's | |
| # API the process would bind to whichever loaded first. | |
| # | |
| # Written with `if`, not `grep -q … && { exit 1; }`: under `bash -e` | |
| # that compound returns non-zero exactly when grep finds NOTHING — | |
| # which is the outcome we want — and the step fails on success. | |
| c=$(find . -name libwayland-client.so -type f | head -1) | |
| s=$(find . -name libwayland-server.so -type f | head -1) | |
| test -n "$c" && test -n "$s" || { echo "::error::a library is missing"; exit 1; } | |
| has() { readelf --dyn-syms -W "$1" | grep -q " $2$"; } | |
| has "$c" wl_display_connect || { echo "::error::client lost wl_display_connect"; exit 1; } | |
| has "$s" wl_display_create || { echo "::error::server lost wl_display_create"; exit 1; } | |
| if has "$c" wl_display_create; then | |
| echo "::error::libwayland-client exports the SERVER api (wl_display_create)"; exit 1 | |
| fi | |
| if has "$s" wl_display_connect; then | |
| echo "::error::libwayland-server exports the CLIENT api (wl_display_connect)"; exit 1 | |
| fi | |
| echo "client and server APIs are disjoint" | |
| - name: mcpp/generated/ matches what the scanner produces | |
| run: | | |
| # The generated protocol code is checked in because it is the | |
| # package's PUBLIC interface (wayland-client.h includes it), so a | |
| # build-time include dir cannot carry it. Checked in means it can | |
| # drift; this is what stops it. | |
| scanner=$(find . -name wayland-scanner -type f -perm -u+x | head -1) | |
| X=upstream/protocol/wayland.xml | |
| mkdir -p /tmp/regen | |
| "$scanner" -s public-code "$X" /tmp/regen/wayland-protocol.c | |
| for side in client server; do | |
| "$scanner" -s $side-header "$X" /tmp/regen/wayland-$side-protocol.h | |
| "$scanner" -s $side-header -c "$X" /tmp/regen/wayland-$side-protocol-core.h | |
| done | |
| for f in /tmp/regen/*; do | |
| n=$(basename "$f") | |
| if ! diff -q "$f" "mcpp/generated/$n" >/dev/null; then | |
| echo "::error file=mcpp/generated/$n::out of date — regenerate it (see mcpp/generated/README.md)" | |
| diff "$f" "mcpp/generated/$n" | head -20 | |
| exit 1 | |
| fi | |
| done | |
| echo "generated code matches wayland.xml and this scanner" | |
| - name: the module wrappers match the generator | |
| run: | | |
| # The .cppm files and the -module.h copies are generated from the | |
| # public headers. Checked in, so they can drift; this stops it. | |
| python3 mcpp/tools/genmod.py . | |
| if ! git diff --quiet; then | |
| echo "::error::module wrappers are out of date — run mcpp/tools/genmod.py ." | |
| git diff --stat | |
| exit 1 | |
| fi | |
| echo "module wrappers match" | |
| - name: every module produced an interface | |
| run: | | |
| for m in freedesktop.wayland.client freedesktop.wayland.server freedesktop.wayland.util; do | |
| find . \( -name "$m.gcm" -o -name "$m.pcm" \) | head -1 | grep -q . \ | |
| || { echo "::error::$m has no module interface"; find . -name '*.gcm' -o -name '*.pcm'; exit 1; } | |
| echo "$m ok" | |
| done | |
| echo "freedesktop.wayland.client, freedesktop.wayland.server and freedesktop.wayland.util all have interfaces" | |
| upstream: | |
| # `upstream/` must be the release tarball, byte for byte. | |
| # | |
| # Checked by COMPARING, not by building: this repository builds one way, | |
| # through mcpp, and keeping a second build system alive just to assert | |
| # something would be a second thing to maintain and a second thing to | |
| # disagree with the first. Comparing is also the stronger check — meson | |
| # succeeding proves the tree still builds, not that nothing was edited. | |
| name: upstream/ is the release tarball, unmodified | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: diff upstream/ against wayland 1.26.0 | |
| run: | | |
| curl -L -fsS -o wayland.tar.xz \ | |
| "https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.26.0/downloads/wayland-1.26.0.tar.xz" | |
| echo "64176eaa46e4969903e286f8e5ef8331affc17fdf03ac9b58381d2b23162b7a3 wayland.tar.xz" | sha256sum -c - | |
| mkdir -p /tmp/pristine | |
| tar -xJf wayland.tar.xz -C /tmp/pristine | |
| # .gitignore lives at the repo root here, not inside upstream/ — it is | |
| # this repository's, covering mcpp's build output. It is the one | |
| # expected difference. | |
| if diff -r --exclude=.gitignore /tmp/pristine/wayland-1.26.0 upstream; then | |
| echo "upstream/ is pristine" | |
| else | |
| echo "::error::upstream/ differs from the wayland 1.26.0 release tarball." | |
| echo "Everything this fork adds belongs under mcpp/." | |
| exit 1 | |
| fi |