Skip to content

the modules carry the organization that owns the protocol #20

the modules carry the organization that owns the protocol

the modules carry the organization that owns the protocol #20

Workflow file for this run

# 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