-
Notifications
You must be signed in to change notification settings - Fork 0
279 lines (253 loc) · 13.6 KB
/
Copy pathci.yml
File metadata and controls
279 lines (253 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# 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.5"
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: every library carries its canonical SONAME
run: |
# `${want%.*}` strips ONE trailing version component, whatever it is:
# the built file is the unversioned `lib*.so` and the SONAME is what
# `readelf -d` reports. The earlier `${want%.0}` form only worked
# because both sonames happened to end in `.0` — libwayland-egl's
# ends in `.1` and would have been searched for by its full name.
for pair in "client:libwayland-client.so.0" \
"server:libwayland-server.so.0" \
"egl:libwayland-egl.so.1" \
"cursor:libwayland-cursor.so.0"; do
member=${pair%%:*}; want=${pair#*:}
so=$(find . -name "${want%.*}" -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: libwayland-egl carries its four functions and nothing else's
run: |
# The whole public surface, named one by one. It is four entries, so
# a table beats a count: a count would still pass if one function
# were replaced by another.
#
# Mesa's libEGL dlopens nothing here — it carries DT_NEEDED on
# `libwayland-egl.so.1` by name — so a missing symbol surfaces as a
# link failure in a client, far from this repo.
e=$(find . -name libwayland-egl.so -type f | head -1)
test -n "$e" || { echo "::error::libwayland-egl was not built"; exit 1; }
has() { readelf --dyn-syms -W "$1" | grep -q " $2$"; }
for f in wl_egl_window_create wl_egl_window_destroy \
wl_egl_window_resize wl_egl_window_get_attached_size; do
has "$e" "$f" || { echo "::error::libwayland-egl lost $f"; exit 1; }
done
# It must NOT carry the client's API. wayland-egl.c calls no client
# function, so if `wl_display_connect` shows up here the member has
# started linking libwayland-client's objects in — the same
# overlap the check above forbids between client and server.
if has "$e" wl_display_connect; then
echo "::error::libwayland-egl exports the CLIENT api (wl_display_connect)"; exit 1
fi
echo "libwayland-egl exports exactly its own four functions"
- name: libwayland-cursor carries its six functions and no host paths
run: |
c=$(find . -name libwayland-cursor.so -type f | head -1)
test -n "$c" || { echo "::error::libwayland-cursor was not built"; exit 1; }
has() { readelf --dyn-syms -W "$1" | grep -q " $2$"; }
for f in wl_cursor_theme_load wl_cursor_theme_destroy \
wl_cursor_theme_get_cursor wl_cursor_image_get_buffer \
wl_cursor_frame wl_cursor_frame_and_duration; do
has "$c" "$f" || { echo "::error::libwayland-cursor lost $f"; exit 1; }
done
# It DOES link the client, unlike libwayland-egl: wayland-cursor.c
# calls wl_shm_create_pool and friends. A missing DT_NEEDED here would
# mean those calls were satisfied from somewhere else.
readelf -d "$c" | grep -q 'libwayland-client.so.0' || {
echo "::error::libwayland-cursor does not link libwayland-client"; exit 1; }
# And the compiled-in HOST cursor paths are gone. xcursor.c:493 bakes
# in "~/.icons:/usr/share/icons:/usr/share/pixmaps:..." unless
# XCURSORPATH is defined, and after relocation those name the host's
# themes. The recipe empties it so a theme comes from XCURSOR_PATH or
# not at all — and grepping the binary is the only way to see that it
# worked, because a wrong value here fails by SILENTLY WORKING on a
# machine that happens to have themes in /usr/share/icons.
if strings "$c" | grep -qE '/usr/share/icons|/usr/X11R6|xorg-x11'; then
echo "::error::a host cursor path was compiled into libwayland-cursor"
strings "$c" | grep -E '/usr/share/icons|/usr/X11R6|xorg-x11'
exit 1
fi
echo "libwayland-cursor: six functions, links the client, no host paths"
- 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