feat: make test-ui checks the UI files with blueprint-compiler

The interface is hand-written GtkBuilder XML and nothing in the build can see a mistake in it: a
property a class does not have, or a child a container does not accept, is a Gtk-ERROR when the
window is built -- and Gtk-ERROR aborts the process, so a typo in a .ui takes the application down
rather than failing to open a dialog. xml-stripblanks catches malformed XML and nothing else, and
the probes catch the rest only for the windows they open. Three of those aborts happened during
this refactor.

tools/check_ui.py round-trips each file through blueprint-compiler: decompile to blueprint, then
compile that, which type-checks every property and child type against the GIR. The XML stays the
source of truth and stays hand-written -- this is a check, not a compiler, and nothing here is
needed to build the application. Which is why the target is opt-in (no ALL) and the tool is looked
for at configure time rather than required: without it the target is absent, not silently passing.

    $ make -C build test-ui
    check_ui: 5 files in src/ui
      ok    help.ui ... source-form.ui
    check_ui: 5 ok, 0 failed

Injected the exact mistake that cost a run earlier (an AdwEntryRow subtitle) and the check caught
it, with the caret and the "did you mean title?" hint, exiting non-zero. --lint prints the linter's
advice separately: it is advice, and the linter has opinions this project does not share.
This commit is contained in:
Martino Ferrari committed 2026-10-12 10:41:25 +02:00
1 parent 8073e7b3fd
commit 7a982da6c4
4 files changed
+190 -17

No files matched your search

+24 -17
View File
@@ -210,23 +210,30 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
- **`find_accel()` masks the modifiers before comparing.** Lock, Num-Lock and the pointer buttons
all arrive in `GdkModifierType`, so an unmasked compare matches no table row at all once
Caps Lock is on.
- **A UI file is data, and a mistake in it is a runtime abort, not a build failure.** `.ui` files live in
`src/ui/` and are compiled into the binary by the third GResource (`udpscope-ui.gresource.xml`,
`prefix=/org/udpscope/ui`), loaded with `gtk_builder_new_from_resource()`. `preprocess="xml-stripblanks"`
catches a *malformed* file at build time — and that includes an XML rule easy to break by hand: **a
comment may not contain a double hyphen**, so prose written the way every C++ comment in this project
is written (` -- like this -- `) fails the build with a caret under the offending character. Use an em
dash in a UI file. `preprocess="xml-stripblanks"`
catches a *malformed* file at build time -- and nothing else does, which is why it is on every file in
every bundle. What it cannot catch is a file that is valid XML but wrong for GTK: `GtkBuilder` reports
that with a **`Gtk-ERROR`, which aborts the process** by default, so a window built by hand from bad
markup takes the application down rather than failing to appear. The `[help]` probe opening every
window is what catches that. Two specifics already paid for: a `GIcon` property takes an **object**, not
a name (`<property name="icon">foo</property>` is read as a reference to another object and fails with
"Object with ID foo not found" -- the icon is a nested `GThemedIcon`), and a `.ui` big enough to exceed
4095 characters needs `-Wno-overlength-strings` on its generated source, because glib-compile-resources
writes each file as one C string literal and the warning fires on generated *data* (the icon and help
bundles are small enough per file never to need it).
- **A UI file is data, and a mistake in it is a runtime abort, not a build failure.** `.ui` files live
in `src/ui/` and are compiled into the binary by the third GResource (`udpscope-ui.gresource.xml`,
`prefix=/org/udpscope/ui`), loaded with `gtk_builder_new_from_resource()`.
`preprocess="xml-stripblanks"` catches a *malformed* file at build time -- which is why it is on
every file in every bundle -- and that includes an XML rule easy to break by hand: **a comment may
not contain a double hyphen**, so prose written the way every C++ comment in this project is written
(` -- like this -- `) fails the build with a caret under the offending character. Use an em dash in a
UI file.
What it cannot catch is a file that is valid XML but wrong for GTK, and that is what
`cmake --build build --target test-ui` is for: it round-trips every file through
`blueprint-compiler`, which type-checks properties and child types against the GIR -- for *every*
file, rather than for the windows a probe happens to open (see
[build-and-run.md](build-and-run.md)). Without that check, `GtkBuilder` reports such a file with a
**`Gtk-ERROR`, which aborts the process** by default, so a window built from bad markup takes the
application down rather than failing to appear.
Two more paid for: a `GIcon` property takes an **object**, not a name
(`<property name="icon">foo</property>` is read as a reference to another object and fails with
"Object with ID foo not found" -- the icon is a nested `GThemedIcon`); and a `.ui` big enough to
exceed 4095 characters needs `-Wno-overlength-strings` on its generated source, because
glib-compile-resources writes each file as one C string literal and the warning fires on generated
*data*.
- **Deprecated widgets may be the right widget, as long as no deprecated *function* is called.** The
shortcut overview is a `GtkShortcutsWindow` (`gtk/deprecated/`, deprecated in 4.20) because libadwaita's
successor, `AdwShortcutsDialog`, cannot draw a mouse gesture: `AdwShortcutsItem` has no icon property at