From 1f3a223fe63815ca45037c590186534156b8659f Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 31 Aug 2026 05:38:21 +0800 Subject: [PATCH 1/6] feat: wlroots 0.20.2, and the two packages the pango line was waiting on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit THE COMPOSITOR GATE IS OPEN. Everything wlroots needs was already here — wayland-server/client/protocols/scanner/egl, libdrm, pixman, xkbcommon, libinput, libudev, libseat, egl, glesv2, gbm, libdisplay-info — and this is the package that spends them. wlroots.wlroots 0.20.2 fork, six generators in build.mcpp, no sh, no python compat.pcre2 10.44 glib's GRegex is written against it compat.fribidi 1.0.16 the bidi algorithm pango needs for RTL text WHY TWO OF THE THREE ARE DESCRIPTORS AND ONE IS A FORK The criterion is generators, not line count. pcre2 is 133k lines and needs no fork: the release tarball ships pcre2.h.generic, config.h.generic and pcre2_chartables.c.dist precisely so a build without autotools is possible. fribidi generates seven UCD tables with eight C programs — and ships the output, verified one for one against the seven `#include "*.tab.i"` in its sources. wlroots has six generators and ships none of their output. ⚠️ import wlroots; IS NOT A CONVENIENCE wlroots' 121 public headers contain no `extern "C"`, and two are not valid C++ at all: five declarations use C99 `const float color[static 4]`, and three struct members are named `namespace`, `delete` and `class`. A C++ consumer cannot include them under any arrangement of `extern "C"`. The fork writes C++-safe copies — `[static N]` reduced to `[N]`, keyword members given an `#ifdef __cplusplus` spelling at the same offset with the `#else` arm upstream verbatim — and the module wraps the lot. WHAT THE TESTS ASSERT, AND WHY NOT "IT LINKED" Each check names a specific decision, so a failure says which one broke: wlroots WLR_HAS_* is exactly the default set; wlr_box_intersection computes 60,20 40x30; wlr_scene_rect_set_color is callable (the rewritten [static 4]); namespace_ is reachable from C++; the PINNED pnp.ids resolves ACR to Acer and DEL to Dell — and "JJJ", whose 15-bit key no entry occupies, returns NULL. Not an arbitrary byte triple: PNP_ID keeps the low five bits of each character, so "\x01\x02\x03" packs to the same key as "ABC" and returns AboCom System Inc. pcre2 \p{Han} and \X prove SUPPORT_UNICODE; `.` matching one code point with PCRE2_UTF and one byte without proves UTF-8 mode; and byte 0xE9 NOT being alphabetic proves the C-locale character tables, i.e. that the build did not run pcre2_dftables against a machine locale. fribidi three different bidi types from one table; a Hebrew run placed first and reversed in an RTL paragraph; "(" mirroring to ")" while "a" mirrors to nothing; ALEF right-joining and BEH dual-joining. Also: the wayland-protocols example moves to 1.49.1 and now static_asserts on the enum headers that release added. Verified on both toolchains from clean trees. wlroots' own CI is green on both legs; its fork release and all three CN mirrors are published. --- mcpp.toml | 3 + pkgs/c/compat.fribidi.lua | 160 ++++++++++ pkgs/c/compat.pcre2.lua | 301 ++++++++++++++++++ pkgs/w/wlroots.wlroots.lua | 124 ++++++++ tests/examples/fribidi/mcpp.toml | 11 + tests/examples/fribidi/tests/fribidi.cpp | 181 +++++++++++ tests/examples/pcre2/mcpp.toml | 11 + tests/examples/pcre2/tests/pcre2.cpp | 160 ++++++++++ tests/examples/wayland-protocols/mcpp.toml | 2 +- .../tests/wayland-protocols.cpp | 19 ++ tests/examples/wlroots/mcpp.toml | 18 ++ tests/examples/wlroots/tests/wlroots.cpp | 169 ++++++++++ 12 files changed, 1158 insertions(+), 1 deletion(-) create mode 100644 pkgs/c/compat.fribidi.lua create mode 100644 pkgs/c/compat.pcre2.lua create mode 100644 pkgs/w/wlroots.wlroots.lua create mode 100644 tests/examples/fribidi/mcpp.toml create mode 100644 tests/examples/fribidi/tests/fribidi.cpp create mode 100644 tests/examples/pcre2/mcpp.toml create mode 100644 tests/examples/pcre2/tests/pcre2.cpp create mode 100644 tests/examples/wlroots/mcpp.toml create mode 100644 tests/examples/wlroots/tests/wlroots.cpp diff --git a/mcpp.toml b/mcpp.toml index 7c0ccc0..5b33e5c 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -101,6 +101,9 @@ members = [ "tests/examples/mtdev", "tests/examples/pixman", "tests/examples/wayland-protocols", + "tests/examples/wlroots", + "tests/examples/pcre2", + "tests/examples/fribidi", "tests/examples/libgbm", "tests/examples/libpng", "tests/examples/libwebp", diff --git a/pkgs/c/compat.fribidi.lua b/pkgs/c/compat.fribidi.lua new file mode 100644 index 0000000..1ee5351 --- /dev/null +++ b/pkgs/c/compat.fribidi.lua @@ -0,0 +1,160 @@ +-- compat.fribidi — the Unicode bidirectional algorithm. +-- +-- THE GATE ON pango. Arabic and Hebrew text is stored in logical order and +-- drawn in visual order, and working out which run goes which way is UAX #9 — +-- a specified algorithm with a conformance test suite, not something a text +-- layout engine reimplements. pango's meson makes it a hard dependency, so +-- nothing above it renders right-to-left text without this. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- WHY AN INLINE DESCRIPTOR AND NOT A FORK +-- +-- fribidi generates seven tables from the Unicode Character Database, with +-- eight C programs under `gen.tab/` that are compiled and RUN during a normal +-- build. That is exactly the shape that forced forks for libevdev, libxkbcommon +-- and libdisplay-info — except that the RELEASE TARBALL SHIPS THE OUTPUT: +-- +-- lib/arabic-misc.tab.i lib/brackets-type.tab.i +-- lib/arabic-shaping.tab.i lib/joining-type.tab.i +-- lib/bidi-type.tab.i lib/mirroring.tab.i +-- lib/brackets.tab.i +-- lib/fribidi-unicode-version.h +-- +-- Verified: the seven `#include "*.tab.i"` in `lib/*.c` are matched one for one +-- by files in the tarball, and `fribidi-unicode-version.h` — which meson also +-- generates — is there too. So there is nothing to generate and no fork to +-- justify. The criterion has always been "does something have to be RUN", and +-- here the answer is no. +-- +-- Only `fribidi-config.h` is missing, because it is a `configure_file` +-- substitution rather than a generated table, and every value in it is a +-- property of this build. It is written out below. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- kind = "lib", NOT "shared" +-- +-- Upstream ships libfribidi.so.0. Nothing dlopens it — pango links it at build +-- time — so merging the objects avoids a SONAME that would then have to coexist +-- with a distribution's copy. Same choice as freedesktop.libdisplay-info, +-- freedesktop.libevdev and wlroots, and for the same reason. +package = { + spec = "1", + namespace = "compat", + name = "fribidi", + description = "GNU FriBidi 1.0.16 — the Unicode bidirectional algorithm (UAX #9), the dependency pango needs for right-to-left text", + licenses = {"LGPL-2.1-or-later"}, + repo = "https://github.com/fribidi/fribidi", + type = "package", + + xpm = { + linux = { + ["1.0.16"] = { + url = { + GLOBAL = "https://github.com/fribidi/fribidi/releases/download/v1.0.16/fribidi-1.0.16.tar.xz", + CN = "https://gitcode.com/mcpp-res/fribidi/releases/download/1.0.16/fribidi-1.0.16.tar.xz", + }, + sha256 = "1b1cde5b235d40479e91be2f0e88a309e3214c8ab470ec8a2744d82a5a9ea05c", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + + -- `*/lib` carries both the public headers and the seven `.tab.i` + -- tables, which the sources include by bare name. + include_dirs = { "mcpp_generated", "*/lib", "*" }, + + generated_files = { + -- meson's `configure_file` output. Upstream's template + -- (lib/fribidi-config.h.in) substitutes ten placeholders; nine are + -- constants of this release and the tenth is sizeof(int). + ["mcpp_generated/fribidi-config.h"] = [[ +/* compat.fribidi: meson's configure_file output for lib/fribidi-config.h.in, + written out because every substitution is a constant of this release or a + property of the target. */ +#ifndef FRIBIDI_CONFIG_H +#define FRIBIDI_CONFIG_H + +#define FRIBIDI "fribidi" +#define FRIBIDI_NAME "GNU FriBidi" +#define FRIBIDI_BUGREPORT "https://github.com/fribidi/fribidi/issues/new" + +#define FRIBIDI_VERSION "1.0.16" +#define FRIBIDI_MAJOR_VERSION 1 +#define FRIBIDI_MINOR_VERSION 0 +#define FRIBIDI_MICRO_VERSION 16 + +/* meson.build:16 — bumped by upstream when the ABI changes, and reported + through fribidi_version_info. Not derived from the release number. */ +#define FRIBIDI_INTERFACE_VERSION 4 +#define FRIBIDI_INTERFACE_VERSION_STRING "4" + +/* Every target this index builds for is ILP32-or-LP64 with a 32-bit int. + fribidi uses it only to size FriBidiLevel arithmetic. */ +#define FRIBIDI_SIZEOF_INT 4 + +/* @FRIBIDI_MSVC_BUILD_PLACEHOLDER@ expands to nothing on a non-MSVC build, + which is what it is doing here by being absent. */ + +#endif /* FRIBIDI_CONFIG_H */ +]], + + -- The autotools-style probe answers. fribidi reads five of these + -- and every one is `#ifdef`-tested, so "defined" is the whole + -- statement and the value is decoration. + ["mcpp_generated/config.h"] = [[ +#ifndef MCPP_FRIBIDI_CONFIG_H +#define MCPP_FRIBIDI_CONFIG_H + +/* HAVE_FRIBIDI_CONFIG_H makes fribidi-common.h include the header above + instead of falling back to its built-in defaults — which are for a + hand-built copy and get the version wrong. */ +#define HAVE_FRIBIDI_CONFIG_H 1 + +/* HAVE_STRINGIZE says the preprocessor supports `#x`, which every C99 + compiler does. Without it fribidi-common.h takes a K&R path. */ +#define HAVE_STRINGIZE 1 + +#define HAVE_STDLIB_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_MEMORY_H 1 +#define SIZEOF_INT 4 + +/* HAVE_FRIBIDI_CUSTOM_H and HAVE_FRIBIDI_UNICODE_VERSION_H are + DELIBERATELY ABSENT rather than 0. + - fribidi_custom.h does not exist here (it is a downstream hook), and + `#ifdef HAVE_FRIBIDI_CUSTOM_H` would then include a missing file. + - fribidi-unicode-version.h DOES exist in the tarball, but the macro that + guards it is only consulted on the autotools path; the meson path — the + one whose file layout this package reproduces — includes it directly. */ + +#endif +]], + }, + + -- lib/*.c, all eighteen. There is nothing to exclude: the charset + -- converters (cp1255, cp1256, iso8859-6/8, cap-rtl) are part of the + -- library upstream ships, `fribidi-deprecated.c` keeps the ABI, and + -- the `bin/` programs (fribidi_benchmark, the CLI) are not here for + -- the usual reason — a dependency that provides `main` collides with + -- the consumer's. + sources = { "*/lib/*.c" }, + + cflags = { + "-DHAVE_CONFIG_H", + "-D_GNU_SOURCE", + -- Upstream's own, from lib/meson.build: without it every public + -- symbol would be hidden and pango would fail to link. + "-DFRIBIDI_BUILD", + -- pango is linked into shared objects, so the objects must be + -- position independent. + "-fPIC", + }, + + targets = { fribidi = { kind = "lib" } }, + }, +} diff --git a/pkgs/c/compat.pcre2.lua b/pkgs/c/compat.pcre2.lua new file mode 100644 index 0000000..2caf71c --- /dev/null +++ b/pkgs/c/compat.pcre2.lua @@ -0,0 +1,301 @@ +-- compat.pcre2 — Perl-compatible regular expressions, second edition. +-- +-- THE GATE ON glib. `glib/gregex.c` is written against pcre2's 8-bit API and +-- glib's meson makes it a hard dependency (`libpcre2-8`, required: true), so +-- nothing in the GLib/GObject/Pango stack can be built without it. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- WHY AN INLINE DESCRIPTOR AND NOT A FORK +-- +-- The criterion in this index is GENERATORS, not line count — 133k lines here +-- against cairo's 104k, and cairo needed no fork either. pcre2 has three +-- generated inputs and the release tarball SHIPS ALL THREE, which is +-- deliberate on upstream's part: they exist so a build without autotools or +-- CMake is possible. +-- +-- src/pcre2.h.generic the public header +-- src/config.h.generic the probe answers +-- src/pcre2_chartables.c.dist the default character tables +-- +-- The last one is the interesting case. Upstream normally builds a +-- `pcre2_dftables` program and RUNS IT to emit `pcre2_chartables.c` from the +-- build machine's C library locale — the same build-machine-dependence that +-- made libdisplay-info and wlroots pin their `pnp.ids`. `.dist` is the C-locale +-- output, checked in by upstream precisely so a build need not run a program to +-- get a deterministic answer. Using it is both simpler AND more reproducible. +-- +-- ⚠️ pcre2.h AND config.h ARE WRITTEN OUT HERE rather than copied from the +-- `.generic` files, because both encode choices this package is making: +-- SUPPORT_UNICODE and the code-unit width are not properties of pcre2, they +-- are properties of the library this index wants glib to link. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ONE CODE-UNIT WIDTH, AND WHY THAT IS NOT A LIMITATION +-- +-- pcre2 compiles the SAME sources once per code-unit width, with every public +-- symbol renamed by `PCRE2_SUFFIX` — so libpcre2-8, -16 and -32 are three +-- libraries with disjoint symbol sets built from one directory. Building all +-- three would triple the object count for consumers that want one. +-- +-- 8 is the width glib uses, and it is the width UTF-8 needs. A consumer that +-- wanted 16- or 32-bit would need its own package, not a feature here: the +-- symbol names differ, so the two cannot coexist in one target anyway. +-- +-- ⚠️ `PCRE2_CODE_UNIT_WIDTH` must ALSO be defined by the consumer before +-- `#include `, because the header uses it to pick which set of +-- declarations to expose. Omitting it is a compile error that says so: +-- "PCRE2_CODE_UNIT_WIDTH must be defined before including pcre2.h" +-- glib does this itself (`#define PCRE2_CODE_UNIT_WIDTH 8` in gregex.c), so +-- nothing extra is needed for the stack this package exists for. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- JIT IS OFF +-- +-- `pcre2_jit_compile.c` IS compiled — it has to be, because +-- `pcre2_jit_compile()` and friends are part of the API and must exist as +-- symbols — but without SUPPORT_JIT they are stubs that return +-- PCRE2_ERROR_JIT_BADOPTION. That is upstream's own arrangement, not a +-- mutilation: `pcre2_config(PCRE2_CONFIG_JIT)` reports 0 and every caller that +-- checks (glib does) takes the interpreter path. +-- +-- The reason to leave it off is that the JIT is a per-architecture assembler +-- (sljit) with its own execution-memory allocator, and enabling it on a target +-- this index has not tested would trade a working regex engine for a possible +-- SIGSEGV. It can be turned on later by one line, once there is a test that +-- would notice. +package = { + spec = "1", + namespace = "compat", + name = "pcre2", + description = "PCRE2 10.44 — 8-bit Perl-compatible regular expressions with Unicode properties, the dependency glib's GRegex is written against", + licenses = {"BSD-3-Clause"}, + repo = "https://github.com/PCRE2Project/pcre2", + type = "package", + + xpm = { + linux = { + ["10.44"] = { + url = { + GLOBAL = "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.44/pcre2-10.44.tar.gz", + CN = "https://gitcode.com/mcpp-res/pcre2/releases/download/10.44/pcre2-10.44.tar.gz", + }, + sha256 = "86b9cb0aa3bcb7994faa88018292bc704cdbb708e785f7c74352ff6ea7d3175b", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + + -- `*` is the extract directory. `*/src` puts `pcre2_chartables.c.dist` + -- and the internal headers on the path; `mcpp_generated` carries the + -- two headers written below, and comes FIRST so `pcre2.h` resolves to + -- ours rather than to any host copy. + include_dirs = { "mcpp_generated", "*/src", "*" }, + + generated_files = { + -- The public header. Upstream's `pcre2.h.in` is a template whose + -- only substitutions are the version numbers and, in the `.generic` + -- form, nothing else — so this is that file's content with the + -- three version macros filled in. + -- + -- Rather than reproduce 700 lines of declarations, this includes + -- the `.generic` copy upstream ships and supplies only what the + -- template would have substituted. `#include` does not care about + -- the file extension. + ["mcpp_generated/pcre2.h"] = [[ +#ifndef MCPP_PCRE2_H +#define MCPP_PCRE2_H +/* compat.pcre2: upstream's own pre-substituted public header. + * + * pcre2.h.generic is what upstream ships for builds that do not run configure, + * and it is byte-identical to what autotools would produce for this version — + * the only substitutions in pcre2.h.in are the three version numbers, and the + * .generic copy already has them. Including it rather than copying it means a + * version bump cannot leave a stale duplicate behind. */ +#include "pcre2.h.generic" +#endif +]], + + -- The probe answers. Every one describes THIS package's build, not + -- pcre2's defaults — the distinction that turned eight undefined + -- references into an afternoon on fontconfig. + ["mcpp_generated/config.h"] = [[ +#ifndef MCPP_PCRE2_CONFIG_H +#define MCPP_PCRE2_CONFIG_H + +/* ── what this build supports ─────────────────────────────────────────── */ + +/* SUPPORT_UNICODE brings in pcre2_ucd.c's property tables (about 100 KB) and + * makes \p{...}, \X and PCRE2_UTF work. glib's GRegex exposes all three + * through its own API, so this is not optional for the stack this package + * exists for. */ +#define SUPPORT_UNICODE 1 + +/* SUPPORT_JIT is DELIBERATELY ABSENT rather than 0. + * + * pcre2 tests it with `#ifdef SUPPORT_JIT` (pcre2_jit_compile.c:60 and + * pcre2_internal.h), so `#define SUPPORT_JIT 0` would say YES — the same trap + * that cost cairo an hour of silently-empty drawing. Absent means absent. + * + * pcre2_jit_compile.c is still COMPILED; without this macro it provides the + * public JIT entry points as stubs returning PCRE2_ERROR_JIT_BADOPTION, which + * is upstream's arrangement and what pcre2_config(PCRE2_CONFIG_JIT) reports. */ + +/* Same rule: these are all `#ifdef`-tested and so are left undefined. + * SUPPORT_PCRE2GREP_JIT, SUPPORT_PCRE2GREP_CALLOUT — pcre2grep only + * SUPPORT_LIBBZ2, SUPPORT_LIBZ, SUPPORT_LIBEDIT, + * SUPPORT_LIBREADLINE — pcre2grep/pcre2test only + * EBCDIC, EBCDIC_NL25 — not this century + * NEVER_BACKSLASH_C, PCRE2_DEBUG — behaviour changes + * SUPPORT_VALGRIND — instrumentation + */ + +/* ── the numeric limits, upstream's own defaults ──────────────────────── */ +/* Not tuned: these are the values every distribution ships, and a consumer + * that needs different ones sets them per-match through the match context. */ +#define HEAP_LIMIT 20000000 +#define LINK_SIZE 2 +#define MATCH_LIMIT 10000000 +#define MATCH_LIMIT_DEPTH MATCH_LIMIT +#define MAX_NAME_COUNT 10000 +#define MAX_NAME_SIZE 32 +#define MAX_VARLOOKBEHIND 255 +#define PARENS_NEST_LIMIT 250 +#define NEWLINE_DEFAULT 2 /* LF — the Unix convention, and glib's assumption */ + +/* ── the C library ────────────────────────────────────────────────────── */ +/* glibc and any musl this index would target have had all of these for + * decades. They are `#ifdef`-tested, so defining them to 1 and defining them + * at all are the same statement. */ +#define HAVE_ATTRIBUTE_UNINITIALIZED 1 +#define HAVE_BUILTIN_MUL_OVERFLOW 1 +#define HAVE_DIRENT_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LIMITS_H 1 +#define HAVE_MEMFD_CREATE 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MKOSTEMP 1 +#define HAVE_SECURE_GETENV 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRERROR 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_WAIT_H 1 +#define HAVE_UNISTD_H 1 + +/* Identification, used by pcre2_config(PCRE2_CONFIG_VERSION) and in error + * text. Kept exactly in step with the tarball this descriptor names. */ +#define PACKAGE "pcre2" +#define PACKAGE_NAME "PCRE2" +#define PACKAGE_STRING "PCRE2 10.44" +#define PACKAGE_TARNAME "pcre2" +#define PACKAGE_VERSION "10.44" +#define VERSION "10.44" + +/* ── the export decoration ────────────────────────────────────────────── */ + +/* PCRE2_EXPORT must be DEFINED, and empty is the right value. + * + * pcre2_internal.h:150 builds the declaration specifier out of it — + * `#define PCRE2_EXP_DECL extern PCRE2_EXPORT` — so leaving it out does not + * mean "no decoration", it means the token survives into the source and every + * exported declaration becomes a syntax error: + * + * pcre2.h.generic:800: error: expected ';' before 'const' + * PCRE2_EXP_DECL const uint8_t *PCRE2_CALL_CONVENTION + * + * — reported against pcre2's own header, which reads like a broken tarball. + * Empty is what upstream's config.h.generic uses on Unix: the objects merge + * into the consumer (kind = "lib"), so there is no shared-library boundary to + * decorate and default visibility is correct. + * + * PCRE2_EXP_DEFN and PCRE2_STATIC stay undefined for the same reason + * config.h.generic leaves them undefined on Unix: both are Windows or + * special-environment hooks, and pcre2_internal.h derives PCRE2_EXP_DEFN from + * PCRE2_EXP_DECL when it is absent. */ +#define PCRE2_EXPORT + +#endif +]], + + -- The default character tables. Upstream builds and RUNS + -- `pcre2_dftables` to produce this file from the build machine's + -- locale; `.dist` is the C-locale output they ship so that nobody + -- has to. Including it keeps the answer independent of where the + -- package was built — the same reasoning that pins `pnp.ids` in + -- freedesktop.libdisplay-info and wlroots. + ["mcpp_generated/pcre2_chartables.c"] = [[ +/* compat.pcre2: upstream's C-locale default tables, compiled as-is. + * + * Upstream's normal path compiles `pcre2_dftables` and runs it against the + * BUILD MACHINE's locale, so `isalpha()` in a Turkish locale would produce a + * different table than in C. `.dist` is the C-locale answer, checked in by + * upstream for exactly this reason. + * + * Found through the "*"-glob src directory on the include path; #include does + * not care about the file extension. */ +#include "pcre2_chartables.c.dist" +]], + }, + + -- Upstream's CMakeLists PCRE2_SOURCES, in order, minus + -- `pcre2_chartables.c` (generated above). The programs — pcre2test, + -- pcre2grep, pcre2demo, the fuzzer, the JIT test — are not here: they + -- have their own `main`, and a dependency that provides one collides + -- with the consumer's (this index has measured that). + -- + -- `pcre2posix.c` is also absent. It is a separate library upstream + -- (libpcre2-posix) whose symbols are regcomp/regexec — names glibc + -- already defines — so merging it into a consumer would be a + -- duplicate-symbol trap for a POSIX API nothing here asks for. + sources = { + "*/src/pcre2_auto_possess.c", + "mcpp_generated/pcre2_chartables.c", + "*/src/pcre2_chkdint.c", + "*/src/pcre2_compile.c", + "*/src/pcre2_config.c", + "*/src/pcre2_context.c", + "*/src/pcre2_convert.c", + "*/src/pcre2_dfa_match.c", + "*/src/pcre2_error.c", + "*/src/pcre2_extuni.c", + "*/src/pcre2_find_bracket.c", + "*/src/pcre2_jit_compile.c", + "*/src/pcre2_maketables.c", + "*/src/pcre2_match.c", + "*/src/pcre2_match_data.c", + "*/src/pcre2_newline.c", + "*/src/pcre2_ord2utf.c", + "*/src/pcre2_pattern_info.c", + "*/src/pcre2_script_run.c", + "*/src/pcre2_serialize.c", + "*/src/pcre2_string_utils.c", + "*/src/pcre2_study.c", + "*/src/pcre2_substitute.c", + "*/src/pcre2_substring.c", + "*/src/pcre2_tables.c", + "*/src/pcre2_ucd.c", + "*/src/pcre2_valid_utf.c", + "*/src/pcre2_xclass.c", + }, + + -- `-DPCRE2_CODE_UNIT_WIDTH=8` is what selects the library being built; + -- every source reads it. `-DHAVE_CONFIG_H` makes them include the + -- config.h above. `-fPIC` because glib is linked into shared objects + -- and a non-PIC object cannot be. + cflags = { + "-DHAVE_CONFIG_H", + "-DPCRE2_CODE_UNIT_WIDTH=8", + "-D_GNU_SOURCE", + "-fPIC", + }, + + targets = { ["pcre2-8"] = { kind = "lib" } }, + }, +} diff --git a/pkgs/w/wlroots.wlroots.lua b/pkgs/w/wlroots.wlroots.lua new file mode 100644 index 0000000..e216d90 --- /dev/null +++ b/pkgs/w/wlroots.wlroots.lua @@ -0,0 +1,124 @@ +-- wlroots — the compositor library this whole stack was built toward. +-- +-- Every dependency wlroots has was already in this index before this +-- descriptor existed: wayland-server/client/protocols/scanner/egl, libdrm, +-- pixman, xkbcommon, libinput, libudev, libseat, egl, glesv2, gbm and +-- libdisplay-info. This is the package that spends them. +-- +-- [dependencies] +-- wlroots.wlroots = "0.20.2" +-- +-- import wlroots; +-- wlr_scene *scene = wlr_scene_create(); +-- +-- ───────────────────────────────────────────────────────────────────────── +-- WHY A FORK +-- +-- Six generators, which is what decides fork-versus-descriptor in this index +-- (the criterion is generators, not line count — cairo is 104k lines and +-- needed no fork): +-- +-- 45 Wayland protocols -> wayland-scanner, three outputs each +-- backend/drm/gen_pnpids.sh +-- render/gles2/shaders/embed.sh +-- configure_file -> include/wlr/config.h, include/config.h, +-- include/wlr/version.h +-- +-- Three are shell scripts upstream. The fork has no `sh` and no `python`: +-- `build.mcpp` is compiled and run by mcpp, so each generator is a function +-- in it. https://github.com/mcpplibs/wlroots +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ⚠️ `import wlroots;` IS NOT A CONVENIENCE — IT IS THE ONLY WAY IN +-- +-- wlroots' 121 public headers contain NOT ONE `extern "C"` block, and two of +-- them are not valid C++ at all. Five declarations use C99's array-parameter +-- form: +-- +-- void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, +-- const float color[static 4]); +-- +-- which g++ reports as `expected primary-expression before 'static'` — and +-- then, the parse never recovering, every LATER declaration in the file as +-- "has not been declared", which sends you looking for missing feature +-- guards. Three struct members are named `namespace`, `delete` and `class`. +-- +-- So a C++ consumer cannot `#include ` under any +-- arrangement of `extern "C"`. The fork's build program writes C++-safe copies +-- of the affected headers — `[static N]` reduced to `[N]`, the keyword members +-- given an `#ifdef __cplusplus` spelling at the same offset, the `#else` arm +-- upstream verbatim — and the module wraps the lot. +-- +-- upstream C C++ +-- wlr_layer_surface_v1::namespace ::namespace_ +-- wlr_input_method_v2::delete ::delete_ +-- +-- Two limits, both inherent rather than defects in the wrapper: +-- +-- * MACROS DO NOT CROSS A MODULE BOUNDARY. `WLR_HAS_*`, `wl_container_of` +-- and `wl_list_for_each` come from headers. `#include ` +-- alongside the import is safe — it is nothing but `#define`s. +-- * A HEADER THAT DECLARES SOMETHING MUST NOT BE INCLUDED ALONGSIDE THE +-- MODULE. `` has no `extern "C"`, so including it gives +-- those three names C++ linkage while the module's have C linkage, and the +-- link fails with ``undefined reference to `wlr_version_get_major()'`` — +-- the parentheses in that message are the tell. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- FEATURES +-- +-- wlroots.wlroots = { version = "0.20.2", default-features = false, +-- features = ["session", "drm"] } +-- +-- Default: `drm`, `libinput`, `session`, `gles2`, `gbm`; `udmabuf` is also +-- available. `drm` and `libinput` both require `session`, and the build +-- program says so at configure time rather than letting it become a link +-- failure naming `wlr_session_*`. +-- +-- Not offered, each for a stated reason rather than an omission: +-- +-- vulkan-renderer needs glslang to compile four shaders to SPIR-V; +-- glslang is not in this index, and a feature that +-- cannot build is worse than an absent one +-- x11-backend needs xcb + xcb-errors; the point of this stack is +-- xwayland a compositor that does not drag X11 in +-- color-management needs lcms2; `color_fallback.c` is built instead, +-- which is upstream's own arrangement without it +-- +-- ───────────────────────────────────────────────────────────────────────── +-- THE PINNED pnp.ids +-- +-- The DRM backend turns a monitor's three-letter PNP id into a manufacturer +-- name from a table generated at build time. Upstream's `gen_pnpids.sh` reads +-- `/usr/share/hwdata/pnp.ids` OFF THE BUILD MACHINE, so the answer would +-- depend on where the package was built — the same trap +-- `freedesktop.libdisplay-info` documents. The fork pins hwdata v0.410 and +-- checks it in beside the generator. +-- +-- The fork's generator is also stricter than upstream's: `PNP_ID` keeps only +-- the low five bits of each character, so ids differing in case collide — +-- hwdata really does carry `inu` alongside uppercase ids — and a collision is +-- reported naming both rather than emitted as two identical `case` labels. +package = { + spec = "1", + namespace = "wlroots", + name = "wlroots", + description = "wlroots 0.20.2 — modular Wayland compositor library, with DRM/libinput/session/GLES2/pixman/GBM built and X11 left out", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/wlroots", + type = "package", + + xpm = { + linux = { + ["0.20.2"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/wlroots/archive/refs/tags/0.20.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/wlroots/releases/download/0.20.2/wlroots-0.20.2.tar.gz", + }, + sha256 = "40c321b79c087d104f9f2b01a61724e0c3a29608a6ff05dda05abd4c73ab929a", + }, + }, + }, + + mcpp = "*/mcpp/wlroots/mcpp.toml", +} diff --git a/tests/examples/fribidi/mcpp.toml b/tests/examples/fribidi/mcpp.toml new file mode 100644 index 0000000..c3a46dd --- /dev/null +++ b/tests/examples/fribidi/mcpp.toml @@ -0,0 +1,11 @@ +# compat.fribidi — the bidi algorithm pango needs for right-to-left text. +[indices] +compat = { path = "../../.." } + +[package] +name = "fribidi-tests" +version = "0.1.0" +standard = "c++23" + +[target.'cfg(linux)'.dependencies.compat] +fribidi = "1.0.16" diff --git a/tests/examples/fribidi/tests/fribidi.cpp b/tests/examples/fribidi/tests/fribidi.cpp new file mode 100644 index 0000000..586bedb --- /dev/null +++ b/tests/examples/fribidi/tests/fribidi.cpp @@ -0,0 +1,181 @@ +// compat.fribidi — the Unicode bidirectional algorithm, on text that needs it. +// +// WHAT THIS ASSERTS AND WHY +// +// The tables are the whole package. fribidi's seven `.tab.i` files encode the +// Unicode Character Database, and a build that compiled none of them would +// still link, still run, and quietly treat Arabic as left-to-right. So every +// check below asks the library a question whose answer is IN a table: +// +// the Unicode version fribidi-unicode-version.h, generated upstream +// bidi character types bidi-type.tab.i +// base direction the resolution algorithm over those types +// visual reordering UAX #9 itself, the reason pango needs this +// mirroring mirroring.tab.i — "(" becomes ")" in an RTL run +// joining types joining-type.tab.i, which shapes Arabic +// +// A pass here means the tarball's pre-generated tables really were compiled — +// the claim that lets this be a descriptor instead of a fork. + +#ifdef __linux__ + +// ⚠️ WRAPPED IN extern "C". fribidi's public headers have no `extern "C"` of +// their own — measured, zero occurrences across fribidi.h and the fifteen +// headers it pulls — so a C++ translation unit mangles every declaration and +// the link fails naming symbols the library plainly contains. Same situation +// as compat.libseat in this index, and the same answer: the consumer wraps. +extern "C" { +#include +} + +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) { + ++failures; + } +} + +} // namespace + +int main() +{ + // ── 1. the version, and the Unicode version behind the tables ──────── + std::printf(" %s\n", fribidi_version_info); + check(std::strstr(fribidi_version_info, "1.0.16") != nullptr, + "fribidi_version_info names 1.0.16, as the descriptor does"); + std::printf(" Unicode %s\n", FRIBIDI_UNICODE_VERSION); + check(FRIBIDI_UNICODE_VERSION[0] >= '1', + "FRIBIDI_UNICODE_VERSION came from the generated header"); + + // ── 2. bidi character types — bidi-type.tab.i ──────────────────────── + // 'a' is L (left-to-right). U+0627 ARABIC LETTER ALEF is AL (arabic + // letter). U+05D0 HEBREW LETTER ALEF is R. Three different answers from + // one table; a build with no table would give the same answer for all. + const FriBidiCharType t_a = fribidi_get_bidi_type(0x0061); + const FriBidiCharType t_alef = fribidi_get_bidi_type(0x0627); + const FriBidiCharType t_hebr = fribidi_get_bidi_type(0x05D0); + std::printf(" types: 'a'=%s U+0627=%s U+05D0=%s\n", + fribidi_get_bidi_type_name(t_a), + fribidi_get_bidi_type_name(t_alef), + fribidi_get_bidi_type_name(t_hebr)); + // There is no FRIBIDI_IS_LTR: the header offers FRIBIDI_IS_RTL and the + // left-to-right case is its negation, which is the honest spelling anyway + // because "not RTL" is what the algorithm actually decides. + check(FRIBIDI_IS_LETTER(t_a) && !FRIBIDI_IS_RTL(t_a), "'a' is a left-to-right letter"); + check(FRIBIDI_IS_ARABIC(t_alef), "U+0627 ARABIC ALEF is an Arabic letter"); + check(FRIBIDI_IS_RTL(t_hebr) && !FRIBIDI_IS_ARABIC(t_hebr), + "U+05D0 HEBREW ALEF is right-to-left but not Arabic"); + + // ── 3. base direction of a paragraph ───────────────────────────────── + // The algorithm scans for the first strong character. A Hebrew-initial + // paragraph is RTL; a Latin-initial one is LTR. This is what pango calls + // to decide which way a line runs. + // ⚠️ fribidi_get_par_direction takes BIDI TYPES, not characters. Passing + // code points compiles — FriBidiCharType and FriBidiChar are both 32-bit + // unsigned — and returns a plausible wrong answer, which is why the check + // below reads the types out of the table first. + { + const FriBidiChar rtl[] = {0x05D0, 0x0020, 0x0041}; // ALEF SPACE A + FriBidiCharType types[3]; + fribidi_get_bidi_types(rtl, 3, types); + check(FRIBIDI_IS_RTL(fribidi_get_par_direction(types, 3)), + "a Hebrew-initial paragraph resolves to RTL"); + + const FriBidiChar ltr[] = {0x0041, 0x0020, 0x05D0}; // A SPACE ALEF + fribidi_get_bidi_types(ltr, 3, types); + check(!FRIBIDI_IS_RTL(fribidi_get_par_direction(types, 3)), + "…and a Latin-initial one to LTR"); + } + + // ── 4. reordering: the point of the library ────────────────────────── + // "abc" + Hebrew "אבג" in logical order, in an RTL paragraph. UAX #9 puts + // the Hebrew run first visually and REVERSES it, because Hebrew is stored + // in logical order and drawn right to left. + // + // This is an INTERMEDIATE QUANTITY, not a rendered image: it says exactly + // where the algorithm put each character, so a wrong answer points at the + // reordering rather than at "the text looked odd". + { + FriBidiChar logical[] = {0x0061, 0x0062, 0x0063, 0x0020, + 0x05D0, 0x05D1, 0x05D2}; // "abc אבג" + const FriBidiStrIndex len = 7; + FriBidiCharType types[7]; + FriBidiBracketType brackets[7]; + FriBidiLevel levels[7]; + FriBidiChar visual[7]; + FriBidiParType par = FRIBIDI_PAR_RTL; + + fribidi_get_bidi_types(logical, len, types); + fribidi_get_bracket_types(logical, len, types, brackets); + const FriBidiLevel max = fribidi_get_par_embedding_levels_ex( + types, brackets, len, &par, levels); + check(max > 0, "fribidi_get_par_embedding_levels_ex resolved levels"); + + std::memcpy(visual, logical, sizeof logical); + FriBidiStrIndex map[7]; + for (FriBidiStrIndex i = 0; i < len; ++i) { + map[i] = i; + } + // warn_unused_result: the return is the maximum level reached, and + // ignoring it is a warning that CI treats as noise worth removing. + const FriBidiLevel rmax = fribidi_reorder_line( + FRIBIDI_FLAGS_DEFAULT, types, len, 0, par, levels, visual, map); + check(rmax > 0, "fribidi_reorder_line reordered at least one level"); + + std::printf(" visual order: "); + for (FriBidiStrIndex i = 0; i < len; ++i) { + std::printf("U+%04X ", visual[i]); + } + std::printf("\n"); + + // In an RTL paragraph the Hebrew run comes first and is reversed: + // U+05D2 U+05D1 U+05D0, then the space, then "abc" left to right. + check(visual[0] == 0x05D2 && visual[1] == 0x05D1 && visual[2] == 0x05D0, + "the Hebrew run is placed first and reversed"); + check(visual[4] == 0x0061 && visual[5] == 0x0062 && visual[6] == 0x0063, + "…and the Latin run keeps its own order"); + } + + // ── 5. mirroring — mirroring.tab.i ─────────────────────────────────── + // In a right-to-left run, "(" must be DRAWN as ")". The glyph substitution + // is a table lookup, and a build without the table returns the input. + { + FriBidiChar mirrored = 0; + const bool got = fribidi_get_mirror_char(0x0028, &mirrored) != 0; + std::printf(" mirror of '(' = U+%04X\n", mirrored); + check(got && mirrored == 0x0029, "'(' mirrors to ')'"); + + FriBidiChar none = 0; + check(fribidi_get_mirror_char(0x0061, &none) == 0, + "…and 'a' has no mirror, rather than mirroring to itself"); + } + + // ── 6. joining types — joining-type.tab.i ──────────────────────────── + // Arabic letters change shape by position. ALEF is right-joining (it links + // to the preceding letter only); BEH is dual-joining. Getting this wrong + // is what makes Arabic render as disconnected letterforms. + { + const FriBidiJoiningType j_alef = fribidi_get_joining_type(0x0627); + const FriBidiJoiningType j_beh = fribidi_get_joining_type(0x0628); + std::printf(" joining: ALEF=%s BEH=%s\n", + fribidi_get_joining_type_name(j_alef), + fribidi_get_joining_type_name(j_beh)); + check(FRIBIDI_IS_JOINING_TYPE_R(j_alef), "ARABIC ALEF is right-joining"); + check(FRIBIDI_IS_JOINING_TYPE_D(j_beh), "ARABIC BEH is dual-joining"); + } + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif diff --git a/tests/examples/pcre2/mcpp.toml b/tests/examples/pcre2/mcpp.toml new file mode 100644 index 0000000..ed3c1e0 --- /dev/null +++ b/tests/examples/pcre2/mcpp.toml @@ -0,0 +1,11 @@ +# compat.pcre2 — glib's regex engine, exercised the way glib uses it. +[indices] +compat = { path = "../../.." } + +[package] +name = "pcre2-tests" +version = "0.1.0" +standard = "c++23" + +[target.'cfg(linux)'.dependencies.compat] +pcre2 = "10.44" diff --git a/tests/examples/pcre2/tests/pcre2.cpp b/tests/examples/pcre2/tests/pcre2.cpp new file mode 100644 index 0000000..8cdda1a --- /dev/null +++ b/tests/examples/pcre2/tests/pcre2.cpp @@ -0,0 +1,160 @@ +// compat.pcre2 — exercised the way glib's GRegex uses it. +// +// WHAT THIS ASSERTS AND WHY EACH ONE IS HERE +// +// The failures worth catching in a regex engine are not "it did not link". +// They are the ones where the library builds, links, runs, and gives a subtly +// wrong answer because a probe macro said something untrue. Each check below +// names a specific decision in the descriptor: +// +// compile + match the sources really were compiled, not just linked +// \p{Han} / \X SUPPORT_UNICODE, i.e. that pcre2_ucd.c's tables are in +// PCRE2_UTF that UTF-8 mode works, which is all glib ever uses +// the character tables that pcre2_chartables.c.dist was compiled, and in the +// C locale — the file the descriptor does NOT let a +// `pcre2_dftables` run produce from the build machine +// pcre2_config(JIT) that JIT is OFF and SAYS so, rather than crashing +// capture groups pcre2_substring_*, which glib's g_match_info_fetch is + +#ifdef __linux__ + +// ⚠️ REQUIRED BEFORE THE HEADER, and pcre2.h says so with an #error if it is +// missing. The header uses it to choose which of the three code-unit widths to +// declare, and this package builds the 8-bit one. glib does exactly this in +// gregex.c. +#define PCRE2_CODE_UNIT_WIDTH 8 +#include + +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-60s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) { + ++failures; + } +} + +// Compile a pattern and report whether it matched, so each case below is one +// line. `subject` is UTF-8 throughout, which is the only encoding glib uses. +bool matches(const char *pattern, const char *subject, uint32_t options = 0) +{ + int err = 0; + PCRE2_SIZE off = 0; + pcre2_code *re = pcre2_compile(reinterpret_cast(pattern), + PCRE2_ZERO_TERMINATED, options, &err, &off, nullptr); + if (re == nullptr) { + PCRE2_UCHAR buf[256]; + pcre2_get_error_message(err, buf, sizeof buf); + std::printf(" compile failed at %zu: %s\n", off, reinterpret_cast(buf)); + return false; + } + pcre2_match_data *md = pcre2_match_data_create_from_pattern(re, nullptr); + const int rc = pcre2_match(re, reinterpret_cast(subject), + std::strlen(subject), 0, 0, md, nullptr); + pcre2_match_data_free(md); + pcre2_code_free(re); + return rc > 0; +} + +} // namespace + +int main() +{ + // ── 1. the version the descriptor names ────────────────────────────── + char ver[64] = {}; + pcre2_config(PCRE2_CONFIG_VERSION, ver); + std::printf(" pcre2 %s\n", ver); + check(std::strncmp(ver, "10.44", 5) == 0, + "pcre2_config reports 10.44, the version the descriptor names"); + + // ── 2. it compiles and matches at all ──────────────────────────────── + check(matches("^a+b$", "aaab"), "compile and match a trivial pattern"); + check(!matches("^a+b$", "aaac"), "…and decline one that does not match"); + + // ── 3. SUPPORT_UNICODE ─────────────────────────────────────────────── + // \p{...} is a compile-time ERROR without SUPPORT_UNICODE, not a silent + // mismatch — which is the good case: it fails loudly. The descriptor + // defines the macro (never as 0; pcre2 tests it with #ifdef). + check(matches("\\p{Han}", "漢", PCRE2_UTF | PCRE2_UCP), + "\\p{Han} matches a Han character — SUPPORT_UNICODE is on"); + check(!matches("\\p{Han}", "a", PCRE2_UTF | PCRE2_UCP), + "…and does not match a Latin one"); + + // \X is an extended grapheme cluster: "é" written as e + U+0301 is ONE + // grapheme and two code points. This distinguishes real UCD tables from a + // build that merely accepted the syntax. + check(matches("^\\X$", "e\xCC\x81", PCRE2_UTF), + "\\X treats e + combining acute as one grapheme cluster"); + + // ── 4. UTF-8 mode ──────────────────────────────────────────────────── + // Without PCRE2_UTF, "." matches one BYTE; with it, one code point. A + // three-byte character therefore distinguishes the two modes exactly. + check(matches("^.$", "漢", PCRE2_UTF), "with PCRE2_UTF, `.` is one code point"); + check(!matches("^.$", "漢"), "…and without it, one byte — so a 3-byte char does not match"); + + // ── 5. the character tables ────────────────────────────────────────── + // `\w` and `[[:alpha:]]` consult pcre2_chartables.c, which upstream would + // otherwise produce by RUNNING pcre2_dftables against the build machine's + // locale. The descriptor compiles upstream's C-locale `.dist` instead, so + // these answers cannot depend on where the package was built. + // + // The discriminating case is the second one: in a C locale, byte 0xE9 + // (é in Latin-1) is NOT alphabetic. In an fr_FR.ISO-8859-1 locale it is. + check(matches("^\\w+$", "abc_123"), "\\w accepts letters, digits and underscore"); + check(!matches("^[[:alpha:]]$", "\xE9"), + "…and byte 0xE9 is not alphabetic, i.e. the tables are C-locale"); + + // ── 6. JIT is off, and says so ─────────────────────────────────────── + // The descriptor compiles pcre2_jit_compile.c without SUPPORT_JIT, so the + // entry points exist as stubs. A consumer that checks — glib does — takes + // the interpreter path. The failure this catches is the other one: a build + // that claims JIT and then has no code behind it. + uint32_t jit = 0; + pcre2_config(PCRE2_CONFIG_JIT, &jit); + std::printf(" pcre2_config(JIT) = %u\n", jit); + check(jit == 0, "pcre2_config reports JIT off, matching the descriptor"); + + int err = 0; + PCRE2_SIZE off = 0; + pcre2_code *re = pcre2_compile(reinterpret_cast("(\\w+)@(\\w+)"), + PCRE2_ZERO_TERMINATED, 0, &err, &off, nullptr); + check(re != nullptr, "compile a pattern with capture groups"); + if (re != nullptr) { + // The JIT stub must FAIL CLEANLY rather than crash or pretend. + const int jrc = pcre2_jit_compile(re, PCRE2_JIT_COMPLETE); + std::printf(" pcre2_jit_compile = %d (PCRE2_ERROR_JIT_BADOPTION is %d)\n", + jrc, PCRE2_ERROR_JIT_BADOPTION); + check(jrc != 0, "…and pcre2_jit_compile declines instead of crashing"); + + // ── 7. capture groups, which is g_match_info_fetch ─────────────── + pcre2_match_data *md = pcre2_match_data_create_from_pattern(re, nullptr); + const char *subject = "user@example"; + const int rc = pcre2_match(re, reinterpret_cast(subject), + std::strlen(subject), 0, 0, md, nullptr); + check(rc == 3, "match yields two capture groups plus the whole match"); + if (rc == 3) { + PCRE2_SIZE len = 0; + PCRE2_UCHAR buf[64] = {}; + len = sizeof buf; + pcre2_substring_copy_bynumber(md, 1, buf, &len); + std::printf(" group 1 = %s\n", reinterpret_cast(buf)); + check(std::strcmp(reinterpret_cast(buf), "user") == 0, + "…and group 1 is the text before the @"); + } + pcre2_match_data_free(md); + pcre2_code_free(re); + } + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif diff --git a/tests/examples/wayland-protocols/mcpp.toml b/tests/examples/wayland-protocols/mcpp.toml index e51aa31..fd5be5a 100644 --- a/tests/examples/wayland-protocols/mcpp.toml +++ b/tests/examples/wayland-protocols/mcpp.toml @@ -31,4 +31,4 @@ standard = "c++23" # is upstream's semantics rather than a defect, and testing it would mean # asserting a link failure. [target.'cfg(linux)'.dependencies.freedesktop] -wayland-protocols-staging = "1.49" +wayland-protocols-staging = "1.49.1" diff --git a/tests/examples/wayland-protocols/tests/wayland-protocols.cpp b/tests/examples/wayland-protocols/tests/wayland-protocols.cpp index 19e50ca..0e037f9 100644 --- a/tests/examples/wayland-protocols/tests/wayland-protocols.cpp +++ b/tests/examples/wayland-protocols/tests/wayland-protocols.cpp @@ -98,3 +98,22 @@ int main() int main() { return 0; } #endif + +// ── 1.49.1: the enum headers upstream installs ─────────────────────────────── +// +// `wayland-scanner enum-header` output, which this fork did not generate before +// 1.49.1. wlroots 0.20 includes these from TEN of its PUBLIC headers, so a +// consumer of wlroots writing the ordinary `#include ` needs them on +// the include path — which is why they belong to THIS package rather than to +// wlroots. +// +// The enum header carries only the protocol's enums: no interface symbols and +// no libwayland dependency. Including it in a translation unit that links +// nothing proves exactly that. +#include +#include + +static_assert(XDG_TOPLEVEL_STATE_MAXIMIZED == 1, + "xdg-shell-enum.h did not come from the scanner"); +static_assert(ZWP_TABLET_TOOL_V2_TYPE_PEN == 0x140, + "tablet-v2-enum.h did not come from the scanner"); diff --git a/tests/examples/wlroots/mcpp.toml b/tests/examples/wlroots/mcpp.toml new file mode 100644 index 0000000..e9e12d8 --- /dev/null +++ b/tests/examples/wlroots/mcpp.toml @@ -0,0 +1,18 @@ +# wlroots — the compositor library, consumed the way a compositor would. +# +# ⚠️ The `wlroots` index redirect is what makes this test the INDEX's rather +# than the published package's. It has to be keyed on the namespace the +# dependency below requests: a redirect declared under a name nothing asks for +# is silently not registered, and the resolution falls back to the remote +# index — at which point this checks a package that is already published +# rather than the descriptor in this PR. +[indices] +wlroots = { path = "../../.." } + +[package] +name = "wlroots-tests" +version = "0.1.0" +standard = "c++23" + +[target.'cfg(linux)'.dependencies.wlroots] +wlroots = "0.20.2" diff --git a/tests/examples/wlroots/tests/wlroots.cpp b/tests/examples/wlroots/tests/wlroots.cpp new file mode 100644 index 0000000..f7db05a --- /dev/null +++ b/tests/examples/wlroots/tests/wlroots.cpp @@ -0,0 +1,169 @@ +// wlroots — what can be asserted without a seat, a GPU or a compositor. +// +// WHAT THIS TEST IS FOR +// +// A compositor library is mostly untestable in CI: creating a backend wants a +// DRM master, a libseat session and an input device, and none of a runner's +// three exist. So the temptation is to assert that it linked, which is worth +// almost nothing — a package can link and still have compiled none of the +// files a consumer needs (this index has measured exactly that). +// +// Everything below is chosen to be an INTERMEDIATE QUANTITY: a value the +// library computes on its own, from data it carries, with no device involved. +// Each one names a specific generator in build.mcpp, so a failure says which +// one broke rather than "wlroots is broken": +// +// version → include/wlr/version.h +// WLR_HAS_* → include/wlr/config.h, and the feature set behind it +// the PNP table → pnpids.c, from the pinned hwdata +// the box maths → util/box.c, i.e. that ordinary sources compiled +// the module itself → 986 `using ::` declarations that must all resolve +// `namespace_` → the C++ keyword-member rewrite +// +// ⚠️ NO extern "C" WRAPPER, and that is the point. wlroots' public headers +// contain not one `extern "C"` block, and two of them are not even valid C++ +// (C99 `[static N]` array parameters). A C++ consumer cannot include them at +// all. `import wlroots;` is not a nicety here — it is the only way in. + +#ifdef __linux__ + +// ⚠️ THE MODULE CANNOT CARRY MACROS, so `WLR_HAS_*` comes from the header. +// Macros are preprocessor entities and nothing exports them. +// +// ⚠️ AND ONLY A MACRO-ONLY HEADER MAY BE INCLUDED ALONGSIDE THE MODULE. +// `` is nothing but `#define`s, so it is safe. `` +// is not: it DECLARES wlr_version_get_major and has no `extern "C"` of its +// own, so including it here gives those three names C++ linkage while the +// module's have C linkage — two different entities, and the link fails with +// +// undefined reference to `wlr_version_get_major()' +// +// naming a function the library plainly contains. The parentheses in that +// message are the tell: a mangled name, i.e. the wrong one. Take declarations +// from the module and macros from the header, never the same name from both. +#include + +import wlroots; + +// `get_pnp_manufacturer` is INTERNAL to the DRM backend — declared in the +// private `backend/drm/util.h`, so it is not in the module and not in any +// installed header. Declared here because the generated table behind it is +// the one thing in this package whose failure looks exactly like success. +// `const char code[static 3]` and `const char *` are the same parameter after +// array-to-pointer decay, so this declaration is ABI-identical to upstream's. +extern "C" const char *get_pnp_manufacturer(const char *code); + +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-62s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) { + ++failures; + } +} + +} // namespace + +int main() +{ + // ── 1. version.h was generated, and says what the manifest says ─────── + std::printf(" wlroots %d.%d.%d\n", wlr_version_get_major(), + wlr_version_get_minor(), wlr_version_get_micro()); + check(wlr_version_get_major() == 0 && wlr_version_get_minor() == 20, + "version.h reports 0.20, the version the manifest declares"); + + // ── 2. config.h reports THIS build's features ──────────────────────── + // Not "some features are on" — the exact default set, so a feature that + // silently stopped being compiled is a failure and not a shrug. + std::printf(" drm=%d libinput=%d session=%d gles2=%d gbm=%d | " + "x11=%d vulkan=%d xwayland=%d\n", + WLR_HAS_DRM_BACKEND, WLR_HAS_LIBINPUT_BACKEND, WLR_HAS_SESSION, + WLR_HAS_GLES2_RENDERER, WLR_HAS_GBM_ALLOCATOR, + WLR_HAS_X11_BACKEND, WLR_HAS_VULKAN_RENDERER, WLR_HAS_XWAYLAND); + check(WLR_HAS_DRM_BACKEND && WLR_HAS_LIBINPUT_BACKEND && WLR_HAS_SESSION + && WLR_HAS_GLES2_RENDERER && WLR_HAS_GBM_ALLOCATOR, + "the five default features are all on"); + check(!WLR_HAS_X11_BACKEND && !WLR_HAS_VULKAN_RENDERER && !WLR_HAS_XWAYLAND, + "…and X11, Vulkan and Xwayland are off, as the manifest says"); + + // ── 3. util/box.c: ordinary sources really were compiled ───────────── + // Pure arithmetic on two rectangles, no device. If the package had linked + // without compiling anything this would not resolve. + wlr_box a{.x = 0, .y = 0, .width = 100, .height = 50}; + wlr_box b{.x = 60, .y = 20, .width = 100, .height = 50}; + wlr_box out{}; + const bool overlap = wlr_box_intersection(&out, &a, &b); + std::printf(" intersection = %d,%d %dx%d\n", out.x, out.y, out.width, out.height); + check(overlap && out.x == 60 && out.y == 20 && out.width == 40 && out.height == 30, + "wlr_box_intersection computes the overlap"); + + check(wlr_box_contains_point(&a, 10, 10) && !wlr_box_contains_point(&a, 10, 90), + "wlr_box_contains_point agrees with the box it was given"); + + // ── 4. the scene graph allocates ───────────────────────────────────── + // wlr_scene_create needs no backend — it is the one non-trivial object a + // test can build. It also proves types/scene/*.c compiled. + wlr_scene *scene = wlr_scene_create(); + check(scene != nullptr, "wlr_scene_create without a backend"); + if (scene != nullptr) { + const float colour[4] = {1.0f, 0.0f, 0.0f, 1.0f}; + wlr_scene_rect *rect = wlr_scene_rect_create(&scene->tree, 32, 16, colour); + check(rect != nullptr, "…and a rect in it"); + // wlr_scene_rect_set_color is one of the five declarations that use + // C99 `[static 4]`. Calling it is what proves the rewritten header + // declares the same function the library defines. + if (rect != nullptr) { + const float other[4] = {0.0f, 1.0f, 0.0f, 1.0f}; + wlr_scene_rect_set_color(rect, other); + check(true, "…and wlr_scene_rect_set_color, whose C99 [static 4] was rewritten"); + } + wlr_scene_node_destroy(&scene->tree.node); + } + + // ── 5. the C++ keyword-member rewrite ──────────────────────────────── + // `struct wlr_layer_surface_v1` has a member C++ cannot name. The header + // this build compiles against renames it under `#ifdef __cplusplus`, at + // the same offset. Reading it through a null-object offset would be UB, so + // this asserts on a real object's storage instead. + wlr_layer_surface_v1 ls{}; + ls.namespace_ = const_cast("panel"); + check(std::strcmp(ls.namespace_, "panel") == 0, + "wlr_layer_surface_v1::namespace_ is reachable from C++"); + + // ── 6. the PNP table generated from the PINNED hwdata ──────────────── + // The failure this catches looks exactly like a passing build: upstream's + // gen_pnpids.sh reads /usr/share/hwdata/pnp.ids off the BUILD MACHINE, so + // the manufacturer a monitor reports would depend on where the package was + // built. `get_pnp_manufacturer` is wlroots-internal rather than exported, + // so it is reached through the DRM backend header's declaration. + // + // "ACR" is the id in the EDID of the Acer monitor freedesktop.libdisplay- + // info tests against, which is where the expected string comes from. + const char *acr = get_pnp_manufacturer("ACR"); + const char *del = get_pnp_manufacturer("DEL"); + // ⚠️ "JJJ", not an arbitrary byte triple. PNP_ID keeps only the LOW FIVE + // BITS of each character, so "\x01\x02\x03" packs to the same key as "ABC" + // and returns AboCom System Inc. — measured, and it is upstream's encoding + // rather than a bug here. An id that is genuinely absent has to be one + // whose 15-bit key no entry occupies; "JJJ" (key 10570) is one of them. + const char *nope = get_pnp_manufacturer("JJJ"); + std::printf(" ACR=%s DEL=%s JJJ=%s\n", acr ? acr : "(null)", + del ? del : "(null)", nope ? nope : "(null)"); + check(acr != nullptr && std::strstr(acr, "Acer") != nullptr, + "the pinned pnp.ids resolved ACR to Acer"); + check(del != nullptr && std::strstr(del, "Dell") != nullptr, "…and DEL to Dell"); + check(nope == nullptr, "…and an unoccupied key returns NULL, not a neighbour"); + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif From ce28f5b1fd8883cf825520f0806e4ecaa4a16d05 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 31 Aug 2026 05:54:24 +0800 Subject: [PATCH 2/6] fix(wlroots): re-cut 0.20.2 with the layer-shell shim fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI on this PR failed with wlr_layer_shell_v1.h:73: use of enum 'zwlr_layer_surface_v1_keyboard_interactivity' without previous declaration from a build that passed locally. The shim that renames the `namespace` argument — layer-shell is the only one of 133 generated protocol headers that is not valid C++ — reached the scanner's real output with `#include_next`, and that is an include which can SUCCEED and deliver the wrong file. A missing file would have said so. The fork now names the file by absolute path (mcpplibs/wlroots, CI green on both toolchains) and 0.20.2 is re-cut. Safe here and only here: the descriptor had never been published, so nothing in the world had extracted it. CN URL moves to the `0.20.2-1` container: gitcode refuses to replace an asset of the same name in an existing release, so the corrected bytes needed a new one. Verified — its sha256 equals the GLOBAL tarball's. --- pkgs/w/wlroots.wlroots.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pkgs/w/wlroots.wlroots.lua b/pkgs/w/wlroots.wlroots.lua index e216d90..d45d5ab 100644 --- a/pkgs/w/wlroots.wlroots.lua +++ b/pkgs/w/wlroots.wlroots.lua @@ -113,9 +113,18 @@ package = { ["0.20.2"] = { url = { GLOBAL = "https://github.com/mcpplibs/wlroots/archive/refs/tags/0.20.2.tar.gz", - CN = "https://gitcode.com/mcpp-res/wlroots/releases/download/0.20.2/wlroots-0.20.2.tar.gz", + -- ⚠️ `0.20.2-1`, not `0.20.2`. The fork's tag was re-cut once + -- before this descriptor was ever published — nothing + -- depended on it yet, so re-cutting was safe — and gitcode + -- refuses to REPLACE an asset of the same name in an + -- existing release. The container holding the correct bytes + -- is therefore a new one; the `0.20.2` container still holds + -- the superseded asset and is not referenced. + -- + -- Verified: this URL's sha256 equals the GLOBAL one's below. + CN = "https://gitcode.com/mcpp-res/wlroots/releases/download/0.20.2-1/wlroots-0.20.2.tar.gz", }, - sha256 = "40c321b79c087d104f9f2b01a61724e0c3a29608a6ff05dda05abd4c73ab929a", + sha256 = "97d9e014c9b633946f24396630e9e317e385160396e474c883903cc92c4a8299", }, }, }, From d7f13411b5eb4da3ba5bf03291005a74c5d26f23 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 31 Aug 2026 06:06:59 +0800 Subject: [PATCH 3/6] fix(wlroots): re-cut 0.20.2 with the generated-header location fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous tarball still put wayland-scanner's headers in the build program's out dir, which is appended AFTER every dependency's include directory. In CI — never locally, across four differently-cleaned trees, both mcpp releases and both standalone and workspace invocations — that produced wlr_output_power_management_v1.h:39: use of enum 'zwlr_output_power_v1_mode' without previous declaration from a header that includes its protocol header on line 5. An include that SUCCEEDED and delivered the wrong file: the failure a search path can have. Every generated header now goes to the directory the manifest names FIRST, which is where config.h already was and for the same reason. Fork CI green on both toolchains. CN container moves to 0.20.2-2: gitcode will not replace an asset of the same name, so each corrected tarball needs a new container. Only the last is referenced. --- pkgs/w/wlroots.wlroots.lua | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/pkgs/w/wlroots.wlroots.lua b/pkgs/w/wlroots.wlroots.lua index d45d5ab..08a6ff3 100644 --- a/pkgs/w/wlroots.wlroots.lua +++ b/pkgs/w/wlroots.wlroots.lua @@ -113,18 +113,23 @@ package = { ["0.20.2"] = { url = { GLOBAL = "https://github.com/mcpplibs/wlroots/archive/refs/tags/0.20.2.tar.gz", - -- ⚠️ `0.20.2-1`, not `0.20.2`. The fork's tag was re-cut once - -- before this descriptor was ever published — nothing - -- depended on it yet, so re-cutting was safe — and gitcode - -- refuses to REPLACE an asset of the same name in an - -- existing release. The container holding the correct bytes - -- is therefore a new one; the `0.20.2` container still holds - -- the superseded asset and is not referenced. + -- ⚠️ THE CONTAINER TAG IS NOT THE PACKAGE VERSION. + -- + -- The fork's tag was re-cut twice while this descriptor was + -- still unpublished — safe precisely because nothing in the + -- world had extracted it yet, which is the only time re- + -- cutting ever is — and gitcode refuses to REPLACE an asset + -- of the same name in an existing release. So each corrected + -- tarball needed a new container: `0.20.2`, `0.20.2-1` and + -- now `0.20.2-2`. Only the last is referenced; the earlier + -- two hold superseded bytes and are left alone rather than + -- deleted, so a stale reference fails a checksum rather than + -- a download. -- -- Verified: this URL's sha256 equals the GLOBAL one's below. - CN = "https://gitcode.com/mcpp-res/wlroots/releases/download/0.20.2-1/wlroots-0.20.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/wlroots/releases/download/0.20.2-2/wlroots-0.20.2.tar.gz", }, - sha256 = "97d9e014c9b633946f24396630e9e317e385160396e474c883903cc92c4a8299", + sha256 = "c8226cdc81b0ec6cbe77d8dc21b5178289a4a276cf946305f92030041daa1afd", }, }, }, From 46adda92362c363f324af18fb0eea1ebaf8131ad Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 31 Aug 2026 06:19:03 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix(wlroots):=20re-cut=200.20.2=20=E2=80=94?= =?UTF-8?q?=20the=20scanner=20now=20runs=20in=20build.mcpp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four CI rounds, one cause, never reproducible locally: wlr_layer_shell_v1.h:73: use of enum 'zwlr_layer_surface_v1_keyboard_interactivity' without previous declaration `mcpp::action` schedules a generated .c before the compile that needs it, because mcpp fixes the source set during prepare. It cannot do the same for a HEADER: nothing declares a dependency on one, and the MODULE INTERFACE UNIT is compiled first since every other TU needs its BMI. So the module was compiled against files the action had created and not yet filled — an include that SUCCEEDED and delivered nothing. A missing file says so; this never did, which is finally what identified it. wayland-scanner now runs inside build.mcpp via posix_spawn (no shell), so every generated header exists before any compiler starts, and each invocation is checked for spawn, exit status AND a non-empty output. Fork CI green on both toolchains; verified here against the published tarball with every store cleared. Also adds §19 to the design doc: the compositor gate, the GObject stack, the four 'include succeeded and gave the wrong file' failures, and the measured boundary of what is left (gdbus-codegen, 8351 lines, blocking gio and therefore pango). --- ...26-08-30-graphics-stack-coverage-design.md | 146 +++++++++++++- mcpp.toml | 3 + pkgs/g/gnome.glib.lua | 93 +++++++++ pkgs/g/gnome.gmodule.lua | 93 +++++++++ pkgs/g/gnome.gobject.lua | 93 +++++++++ pkgs/w/wlroots.wlroots.lua | 8 +- tests/examples/glib/mcpp.toml | 11 ++ tests/examples/glib/tests/glib.cpp | 180 ++++++++++++++++++ tests/examples/gmodule/mcpp.toml | 11 ++ tests/examples/gmodule/tests/gmodule.cpp | 110 +++++++++++ tests/examples/gobject/mcpp.toml | 11 ++ tests/examples/gobject/tests/gobject.cpp | 180 ++++++++++++++++++ 12 files changed, 934 insertions(+), 5 deletions(-) create mode 100644 pkgs/g/gnome.glib.lua create mode 100644 pkgs/g/gnome.gmodule.lua create mode 100644 pkgs/g/gnome.gobject.lua create mode 100644 tests/examples/glib/mcpp.toml create mode 100644 tests/examples/glib/tests/glib.cpp create mode 100644 tests/examples/gmodule/mcpp.toml create mode 100644 tests/examples/gmodule/tests/gmodule.cpp create mode 100644 tests/examples/gobject/mcpp.toml create mode 100644 tests/examples/gobject/tests/gobject.cpp diff --git a/.agents/docs/2026-08-30-graphics-stack-coverage-design.md b/.agents/docs/2026-08-30-graphics-stack-coverage-design.md index 1109950..547d68c 100644 --- a/.agents/docs/2026-08-30-graphics-stack-coverage-design.md +++ b/.agents/docs/2026-08-30-graphics-stack-coverage-design.md @@ -1,6 +1,6 @@ # mcpp 图形栈:从「能跑通」到「能开发」的覆盖面设计 -Date: 2026-08-30 · 前置:[`2026-08-30-gbm-cross-repo-closed-loop-plan.md`](2026-08-30-gbm-cross-repo-closed-loop-plan.md) §19/§20 · **状态:已实现并闭环验证(v1.5,§11 总账 / §14 fork 规范 / §17 桌面栈补齐 / §18 八角度实现后复核)** +Date: 2026-08-30 · 前置:[`2026-08-30-gbm-cross-repo-closed-loop-plan.md`](2026-08-30-gbm-cross-repo-closed-loop-plan.md) §19/§20 · **状态:已实现并闭环验证(v1.6,§11 总账 / §14 fork 规范 / §18 八角度复核 / §19 合成器闸门与 GObject 栈)** ## 0. 这份文档解决什么 @@ -1548,3 +1548,147 @@ tag**——把新内容作为 release 资产发布,旧资产原样保留,零窗 设计时的判断有一半没扛过实现——而这正是「验证要更新到文档」的意义:留下的应该是 **被证伪之后的那一版**。 + +--- + +## 19. 合成器闸门与 GObject 栈(2026-08-31) + +§18 结尾说 wlroots「是『要不要』而不是『能不能』」。这一轮做了,答案是能 —— +连同 pango 那条线上能做的部分,以及**做不到的那一段的确切边界**。 + +| 包 | 版本 | 形态 | 为什么 | +|---|---|---|---| +| `wlroots.wlroots` | 0.20.2 | fork | 六个生成器 | +| `compat.pcre2` | 10.44 | 描述符 | 133k 行,**零生成器**(发布包自带 `.generic`/`.dist`) | +| `compat.fribidi` | 1.0.16 | 描述符 | 有八个生成程序,但**输出随发布包一起发** | +| `gnome.glib` / `gobject` / `gmodule` | 2.82.5 | fork | 六个生成器,含 816 行的 `glib-mkenums` | +| `freedesktop.wayland-protocols-*` | 1.49.1 | 补 enum 头 | 上游装、fork 之前没生成 | + +**判据再次被证明是「生成器」而不是行数**:pcre2 比 cairo 还大 29k 行,仍然是 +描述符;libdisplay-info 两千行是 fork。fribidi 更细一层 —— 它**有**生成器,但 +release tarball 里带着七张表的输出,所以没有东西需要跑。 + +### 19.1 `import wlroots;` 不是便利,是唯一入口 + +这是本轮最值得记的一条。wlroots 的 121 个公共头**没有一个 `extern "C"`**,而且 +其中两个**根本不是合法 C++**: + +```c +void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, + const float color[static 4]); /* C99 专有 */ +``` + +g++ 报 `expected primary-expression before 'static'`,而且解析再也没恢复,于是 +文件里**后面每一条**声明都被报成「has not been declared」—— 把人引向「是不是 +少了 feature 守卫」。另有三个结构体成员叫 `namespace`、`delete`、`class`。 + +所以 C++ 消费者用任何 `extern "C"` 组合都 include 不了这些头。fork 的做法: + +| 上游 C | C++ | +|---|---| +| `[static N]` | `[N]` | +| `wlr_layer_surface_v1::namespace` | `::namespace_` | +| `wlr_input_method_v2::delete` | `::delete_` | + +关键字成员用 `#ifdef __cplusplus` 双臂给出,`#else` 一字不改是上游原文 —— +**同一偏移、同一 ABI,一种语言一种拼法**。C++ 拼不出 `namespace` 这个名字,所以 +给出 `namespace_` 不是改上游 API,而是它在这一侧唯一存在的形态。 + +**与 glib 对照可以看出这不是家风。** glib 没有模块:它的 API 是宏重的 +(`G_DEFINE_TYPE`、`g_signal_connect` 全是宏),而**宏不跨模块边界**,`import` +会把声明给你、把让声明可用的那一半扣下。形态跟着上游的头长什么样,不跟风格。 + +### 19.2 模块的两条固有限制 + +| | | +|---|---| +| 宏不跨模块 | `WLR_HAS_*`、`wl_container_of` 都来自头。`#include ` 与 import 并存是安全的 —— 它只有 `#define` | +| **会声明东西的头不能与模块并存** | `` 没有 `extern "C"`,并存会让那三个名字拿到 C++ 链接,而模块里是 C 链接。链接错误是 ``undefined reference to `wlr_version_get_major()'`` —— **括号就是线索**:名字被修饰了 | + +### 19.3 四个「看起来成功、其实拿错文件」的失败 + +本轮代价最高的一类,全部只在 CI 出现、本地四种清理方式都复现不了: + +1. **`config.h` 是 C 里最挤的文件名。** wlroots 的 include 路径上有**六个**, + 而 `mcpp::include_dir()`(build 程序发的)排在**所有依赖之后** —— 我们那份是 + 59/59。十一个 wlroots 源码读到的是 `compat.libinput` 的,`if (!HAVE_EVENTFD)` + ——wlroots 把它当**普通 C 表达式**用——报 `HAVE_EVENTFD undeclared`。 + +2. **清单里的 `include_dirs` 排在最前**,所以生成的头要写进清单点名的目录。 + 但 mcpp 在**运行 build 程序之前**就构造好命令行,并且**静默丢弃**尚不存在的 + 条目 —— 干净 clone 的第一次构建里,config.h 生成正确然后被无视。 + `include/.gitkeep` 就是为这个提交的。 + +3. 同样的道理适用于**协议头**。先是用 `#include_next` 让 shim 找到真头,CI 报 + `use of enum 'zwlr_layer_surface_v1_keyboard_interactivity' without previous + declaration` —— 一个**成功了但拿到错文件**的 include(文件缺失会明说)。改成 + 绝对路径后,另一个头又以同样方式失败。最终把**所有生成的头**都放进清单首位 + 目录,这一类才根除。 + +4. **本地绿可能链的是别的库。** `mcpp::link_lib("EGL")` 只发一个**标志**,ninja + 拿不到到 `bin/libEGL.so` 的边;本地能过是因为 mcpp subos 里正好有一份 + `libEGL.so.1`,`ldd` 指着它。lld 在干净树上直接说 `unable to find library + -lEGL`。 + +### 19.4 `[feature-deps]` 只对 `kind = "lib"` 有效 + +实测,而且判据很干脆: + +- `kind = "lib"` 的 feature 依赖(libseat、libinput、libudev、libdisplay-info) + **正常** —— 它们的对象并进本包,压根不需要 `-l`。 +- `kind = "shared"` 的(egl、glesv2)**不链接**,报 30 多个 `undefined symbol: + glActiveTexture / eglMakeCurrent`。 + +所以 EGL/GLESv2/gbm 移到普通 `[dependencies]`:它既链接又建立 ninja 边。代价是 +`default-features = false` 也会构建它们;feature 仍然决定**渲染器源码编不编** +和 `WLR_HAS_GLES2_RENDERER` 说什么,那才是消费者观察得到的部分。 + +### 19.5 探测宏:两个方向都踩过 + +§17 记的是「`#ifdef` 测的宏不能写 0」。这轮同时踩到了它和它的反面: + +| 文件 | 测法 | 正确写法 | +|---|---|---| +| glib `config.h` | `#ifdef HAVE_ISSETUGID` | **缺席**。写 `0` 让 glib 调了 glibc 没有的 BSD 接口 | +| wlroots `wlr/config.h` | `#if WLR_HAS_DRM_BACKEND` | **总是定义**,0 或 1。漏掉会让 `#if` 静默为假 | +| glib `gmoduleconf.h.in` | `#if (@X@)` | **值**,所以 0 是对的,省略是语法错 | + +**测法决定,每一次。** 名字长得像不算数。 + +还有一对几乎同名的宏:`USE_SYSTEM_PRINTF`(config.h,**选择**)与 +`GLIB_USING_SYSTEM_PRINTF`(公共 glibconfig.h,只**报告**)。只设第二个,glib +继续调它自带的 gnulib printf,而那部分根本没编 —— 一页 +`undefined reference to _g_gnulib_snprintf`。 + +### 19.6 包无法导出生成的头(2026-08-31 实测) + +两个包的探针,结论干脆: + +| | 消费者看得见吗 | +|---|---| +| `mcpp::include_dir()`(build 程序发的) | ❌ **包私有**,消费者报 `No such file or directory` | +| `[build] include_dirs`(清单里的) | ✅ 传播 | + +**产物本身就是头的包,不能在构建期生成它们。** 这解释了 +`freedesktop.wayland-protocols-*` 为什么把 195 个生成文件签进仓 —— 那不是历史 +包袱,是被迫的。§14 的规则对**自用**生成物成立,对**导出**生成物不成立。 + +1.49.1 就是按这条补的:上游的 meson 会装 `wayland-protocols/-enum.h`, +fork 之前只生成 client/server 两种,而 wlroots 0.20 的**十个公共头**要它们。 + +### 19.7 仍然缺的:gio,以及被它挡住的 pango + +这不是「还没做」,是量过的边界: + +- gio 的六个生成器里有两个是 **`gdbus-codegen`,8,351 行 Python**,把 D-Bus + 接口 XML 变成 GObject skeleton。 +- **七个** gio 源码 include 它的产物,另有**两个**引用那七个。 +- 所以不能绕过它而仍然是 gio;在 `build.mcpp` 里复刻一个 8.3k 行的代码生成器 + 与消费者的需要不成比例。 + +**pango 用 `GListModel`,它在 gio 里。** 所以文本排版那条线停在这里 —— 其余 +四个依赖(harfbuzz、fribidi、fontconfig、cairo)都已在索引中,只差这一环。 + +`vulkan-renderer`(要 glslang)、`x11-backend` / `xwayland`(要 xcb)、 +`color-management`(要 lcms2)同理:每一个都在包里写明「为什么缺席」而不是 +默默不提。 diff --git a/mcpp.toml b/mcpp.toml index 5b33e5c..28e540b 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -104,6 +104,9 @@ members = [ "tests/examples/wlroots", "tests/examples/pcre2", "tests/examples/fribidi", + "tests/examples/glib", + "tests/examples/gobject", + "tests/examples/gmodule", "tests/examples/libgbm", "tests/examples/libpng", "tests/examples/libwebp", diff --git a/pkgs/g/gnome.glib.lua b/pkgs/g/gnome.glib.lua new file mode 100644 index 0000000..1aac299 --- /dev/null +++ b/pkgs/g/gnome.glib.lua @@ -0,0 +1,93 @@ +-- gnome.glib — part of GLib 2.82.5. +-- +-- Upstream ships glib as one source tree producing FOUR separate shared +-- libraries, and this index follows that split: `gnome.glib`, +-- `gnome.gobject` and `gnome.gmodule` are three packages from one fork, +-- because that is what a consumer links. (The fourth, gio, is absent — see +-- below.) +-- +-- ───────────────────────────────────────────────────────────────────────── +-- WHY A FORK +-- +-- Generators, not line count. GLib has six, and `build.mcpp` reimplements all +-- of them, so the tree carries no `sh` and no `python`: +-- +-- gen-visibility-macros.py versions-macros glib/gversionmacros.h +-- gen-visibility-macros.py visibility-macros three *-visibility.h +-- configure_file glibconfig.h +-- configure_file gmodule/gmoduleconf.h +-- gobject/glib-mkenums (816 lines of Python) glib-enumtypes.{h,c} +-- configure_file config.h +-- +-- glib-mkenums is reproduced FOR THE ONE INPUT this build points it at — +-- `glib/gunicode.h`, four enums — rather than wholesale. It reads upstream's +-- `.template` files from the tree so a template change is picked up, and +-- exits non-zero if that header stops yielding four enums: a silent drop +-- would produce a library missing `g_unicode_script_get_type` and the failure +-- would land in a consumer. +-- +-- https://github.com/mcpplibs/glib +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ⚠️ C++ CONSUMERS MUST WRAP THE INCLUDES +-- +-- glib's headers carry their own `G_BEGIN_DECLS`, so this is usually fine — +-- but the generated `glib-enumtypes.h` comes from a template, and wrapping +-- costs nothing: +-- +-- extern "C" { +-- #include +-- } +-- +-- There is no module. glib's API is macro-heavy — `G_DEFINE_TYPE`, +-- `G_OBJECT`, `g_signal_connect` are all macros — and macros do not cross a +-- module boundary, so an `import` would hand a consumer the declarations and +-- withhold the half of the API that makes them usable. Compare +-- `wlroots.wlroots`, where the module is the ONLY way in because the headers +-- are not valid C++ at all: the shape follows what upstream's headers are, +-- not a house style. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ⚠️ gio IS NOT IN THIS INDEX, AND THAT BLOCKS pango +-- +-- Two of gio's six generators are `gdbus-codegen`, an 8,351-line Python +-- program that turns D-Bus interface XML into GObject skeletons. Seven gio +-- sources include its output and two more reference those, so it cannot be +-- dropped without changing what gio is, and reimplementing it in +-- `build.mcpp` is not proportionate. +-- +-- pango uses `GListModel`, which lives in gio, so the text-layout line stops +-- here. Measured and recorded rather than left as an unexplained gap. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- LINUX ONLY +-- +-- The generated `glibconfig.h` fixes `G_OS_UNIX`, the POSIX thread +-- implementation and the poll constants, and `build.mcpp` refuses to run +-- anywhere else rather than emitting a header that is quietly wrong. The +-- upstream tree also contains two `COPYING` symlinks, which a Windows +-- extraction would not survive — one more reason the descriptor offers +-- `linux` alone. +package = { + spec = "1", + namespace = "gnome", + name = "glib", + description = "GLib 2.82.5 — data structures, the main loop, Unicode, GVariant and GRegex", + licenses = {"LGPL-2.1-or-later"}, + repo = "https://github.com/mcpplibs/glib", + type = "package", + + xpm = { + linux = { + ["2.82.5"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/glib/archive/refs/tags/2.82.5.tar.gz", + CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5/glib-2.82.5.tar.gz", + }, + sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", + }, + }, + }, + + mcpp = "*/mcpp/glib/mcpp.toml", +} diff --git a/pkgs/g/gnome.gmodule.lua b/pkgs/g/gnome.gmodule.lua new file mode 100644 index 0000000..e80cb29 --- /dev/null +++ b/pkgs/g/gnome.gmodule.lua @@ -0,0 +1,93 @@ +-- gnome.gmodule — part of GLib 2.82.5. +-- +-- Upstream ships glib as one source tree producing FOUR separate shared +-- libraries, and this index follows that split: `gnome.glib`, +-- `gnome.gobject` and `gnome.gmodule` are three packages from one fork, +-- because that is what a consumer links. (The fourth, gio, is absent — see +-- below.) +-- +-- ───────────────────────────────────────────────────────────────────────── +-- WHY A FORK +-- +-- Generators, not line count. GLib has six, and `build.mcpp` reimplements all +-- of them, so the tree carries no `sh` and no `python`: +-- +-- gen-visibility-macros.py versions-macros glib/gversionmacros.h +-- gen-visibility-macros.py visibility-macros three *-visibility.h +-- configure_file glibconfig.h +-- configure_file gmodule/gmoduleconf.h +-- gobject/glib-mkenums (816 lines of Python) glib-enumtypes.{h,c} +-- configure_file config.h +-- +-- glib-mkenums is reproduced FOR THE ONE INPUT this build points it at — +-- `glib/gunicode.h`, four enums — rather than wholesale. It reads upstream's +-- `.template` files from the tree so a template change is picked up, and +-- exits non-zero if that header stops yielding four enums: a silent drop +-- would produce a library missing `g_unicode_script_get_type` and the failure +-- would land in a consumer. +-- +-- https://github.com/mcpplibs/glib +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ⚠️ C++ CONSUMERS MUST WRAP THE INCLUDES +-- +-- glib's headers carry their own `G_BEGIN_DECLS`, so this is usually fine — +-- but the generated `glib-enumtypes.h` comes from a template, and wrapping +-- costs nothing: +-- +-- extern "C" { +-- #include +-- } +-- +-- There is no module. glib's API is macro-heavy — `G_DEFINE_TYPE`, +-- `G_OBJECT`, `g_signal_connect` are all macros — and macros do not cross a +-- module boundary, so an `import` would hand a consumer the declarations and +-- withhold the half of the API that makes them usable. Compare +-- `wlroots.wlroots`, where the module is the ONLY way in because the headers +-- are not valid C++ at all: the shape follows what upstream's headers are, +-- not a house style. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ⚠️ gio IS NOT IN THIS INDEX, AND THAT BLOCKS pango +-- +-- Two of gio's six generators are `gdbus-codegen`, an 8,351-line Python +-- program that turns D-Bus interface XML into GObject skeletons. Seven gio +-- sources include its output and two more reference those, so it cannot be +-- dropped without changing what gio is, and reimplementing it in +-- `build.mcpp` is not proportionate. +-- +-- pango uses `GListModel`, which lives in gio, so the text-layout line stops +-- here. Measured and recorded rather than left as an unexplained gap. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- LINUX ONLY +-- +-- The generated `glibconfig.h` fixes `G_OS_UNIX`, the POSIX thread +-- implementation and the poll constants, and `build.mcpp` refuses to run +-- anywhere else rather than emitting a header that is quietly wrong. The +-- upstream tree also contains two `COPYING` symlinks, which a Windows +-- extraction would not survive — one more reason the descriptor offers +-- `linux` alone. +package = { + spec = "1", + namespace = "gnome", + name = "gmodule", + description = "GModule 2.82.5 — portable dynamic module loading over dlopen", + licenses = {"LGPL-2.1-or-later"}, + repo = "https://github.com/mcpplibs/glib", + type = "package", + + xpm = { + linux = { + ["2.82.5"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/glib/archive/refs/tags/2.82.5.tar.gz", + CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5/glib-2.82.5.tar.gz", + }, + sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", + }, + }, + }, + + mcpp = "*/mcpp/gmodule/mcpp.toml", +} diff --git a/pkgs/g/gnome.gobject.lua b/pkgs/g/gnome.gobject.lua new file mode 100644 index 0000000..ef0cc1d --- /dev/null +++ b/pkgs/g/gnome.gobject.lua @@ -0,0 +1,93 @@ +-- gnome.gobject — part of GLib 2.82.5. +-- +-- Upstream ships glib as one source tree producing FOUR separate shared +-- libraries, and this index follows that split: `gnome.glib`, +-- `gnome.gobject` and `gnome.gmodule` are three packages from one fork, +-- because that is what a consumer links. (The fourth, gio, is absent — see +-- below.) +-- +-- ───────────────────────────────────────────────────────────────────────── +-- WHY A FORK +-- +-- Generators, not line count. GLib has six, and `build.mcpp` reimplements all +-- of them, so the tree carries no `sh` and no `python`: +-- +-- gen-visibility-macros.py versions-macros glib/gversionmacros.h +-- gen-visibility-macros.py visibility-macros three *-visibility.h +-- configure_file glibconfig.h +-- configure_file gmodule/gmoduleconf.h +-- gobject/glib-mkenums (816 lines of Python) glib-enumtypes.{h,c} +-- configure_file config.h +-- +-- glib-mkenums is reproduced FOR THE ONE INPUT this build points it at — +-- `glib/gunicode.h`, four enums — rather than wholesale. It reads upstream's +-- `.template` files from the tree so a template change is picked up, and +-- exits non-zero if that header stops yielding four enums: a silent drop +-- would produce a library missing `g_unicode_script_get_type` and the failure +-- would land in a consumer. +-- +-- https://github.com/mcpplibs/glib +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ⚠️ C++ CONSUMERS MUST WRAP THE INCLUDES +-- +-- glib's headers carry their own `G_BEGIN_DECLS`, so this is usually fine — +-- but the generated `glib-enumtypes.h` comes from a template, and wrapping +-- costs nothing: +-- +-- extern "C" { +-- #include +-- } +-- +-- There is no module. glib's API is macro-heavy — `G_DEFINE_TYPE`, +-- `G_OBJECT`, `g_signal_connect` are all macros — and macros do not cross a +-- module boundary, so an `import` would hand a consumer the declarations and +-- withhold the half of the API that makes them usable. Compare +-- `wlroots.wlroots`, where the module is the ONLY way in because the headers +-- are not valid C++ at all: the shape follows what upstream's headers are, +-- not a house style. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- ⚠️ gio IS NOT IN THIS INDEX, AND THAT BLOCKS pango +-- +-- Two of gio's six generators are `gdbus-codegen`, an 8,351-line Python +-- program that turns D-Bus interface XML into GObject skeletons. Seven gio +-- sources include its output and two more reference those, so it cannot be +-- dropped without changing what gio is, and reimplementing it in +-- `build.mcpp` is not proportionate. +-- +-- pango uses `GListModel`, which lives in gio, so the text-layout line stops +-- here. Measured and recorded rather than left as an unexplained gap. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- LINUX ONLY +-- +-- The generated `glibconfig.h` fixes `G_OS_UNIX`, the POSIX thread +-- implementation and the poll constants, and `build.mcpp` refuses to run +-- anywhere else rather than emitting a header that is quietly wrong. The +-- upstream tree also contains two `COPYING` symlinks, which a Windows +-- extraction would not survive — one more reason the descriptor offers +-- `linux` alone. +package = { + spec = "1", + namespace = "gnome", + name = "gobject", + description = "GObject 2.82.5 — the GLib type system: GType, signals, properties, closures and GValue", + licenses = {"LGPL-2.1-or-later"}, + repo = "https://github.com/mcpplibs/glib", + type = "package", + + xpm = { + linux = { + ["2.82.5"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/glib/archive/refs/tags/2.82.5.tar.gz", + CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5/glib-2.82.5.tar.gz", + }, + sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", + }, + }, + }, + + mcpp = "*/mcpp/gobject/mcpp.toml", +} diff --git a/pkgs/w/wlroots.wlroots.lua b/pkgs/w/wlroots.wlroots.lua index 08a6ff3..6f3194d 100644 --- a/pkgs/w/wlroots.wlroots.lua +++ b/pkgs/w/wlroots.wlroots.lua @@ -115,21 +115,21 @@ package = { GLOBAL = "https://github.com/mcpplibs/wlroots/archive/refs/tags/0.20.2.tar.gz", -- ⚠️ THE CONTAINER TAG IS NOT THE PACKAGE VERSION. -- - -- The fork's tag was re-cut twice while this descriptor was + -- The fork's tag was re-cut three times while this descriptor was -- still unpublished — safe precisely because nothing in the -- world had extracted it yet, which is the only time re- -- cutting ever is — and gitcode refuses to REPLACE an asset -- of the same name in an existing release. So each corrected -- tarball needed a new container: `0.20.2`, `0.20.2-1` and - -- now `0.20.2-2`. Only the last is referenced; the earlier + -- now `0.20.2-3`. Only the last is referenced; the earlier -- two hold superseded bytes and are left alone rather than -- deleted, so a stale reference fails a checksum rather than -- a download. -- -- Verified: this URL's sha256 equals the GLOBAL one's below. - CN = "https://gitcode.com/mcpp-res/wlroots/releases/download/0.20.2-2/wlroots-0.20.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/wlroots/releases/download/0.20.2-3/wlroots-0.20.2.tar.gz", }, - sha256 = "c8226cdc81b0ec6cbe77d8dc21b5178289a4a276cf946305f92030041daa1afd", + sha256 = "a9a9c2d8604486f655c919c5f94f90f75d04b09710eb3b151f2ec6ea866fd04e", }, }, }, diff --git a/tests/examples/glib/mcpp.toml b/tests/examples/glib/mcpp.toml new file mode 100644 index 0000000..63854f9 --- /dev/null +++ b/tests/examples/glib/mcpp.toml @@ -0,0 +1,11 @@ +# gnome.glib — the generated headers, and the subsystems that depend on them. +[indices] +gnome = { path = "../../.." } + +[package] +name = "glib-tests" +version = "0.1.0" +standard = "c++23" + +[target.'cfg(linux)'.dependencies.gnome] +glib = "2.82.5" diff --git a/tests/examples/glib/tests/glib.cpp b/tests/examples/glib/tests/glib.cpp new file mode 100644 index 0000000..ddd2cb2 --- /dev/null +++ b/tests/examples/glib/tests/glib.cpp @@ -0,0 +1,180 @@ +// glib — the generated headers, and the subsystems that depend on them. +// +// WHAT THIS ASSERTS AND WHY +// +// glib is large and mostly self-evident: if GHashTable were broken the build +// would not have got here. What is NOT self-evident in a fork is whether the +// five generated headers say the right things, because every one of them was +// written out by hand-reimplemented generators. A wrong answer in glibconfig.h +// does not fail to compile — it changes the width of gsize, or the byte order, +// or which printf glib calls. +// +// So each check reads back a value that a generator decided: +// +// glibconfig.h gsize/gssize widths, G_BYTE_ORDER, the format strings +// gversionmacros.h GLIB_VERSION_2_82 exists and encodes 2.82 +// config.h USE_SYSTEM_PRINTF, via a format only glibc gets right +// glib-visibility.h a GLIB_AVAILABLE_IN_2_x symbol is actually exported +// pcre2 wiring GRegex, which is the one external dependency +// +// The subsystem checks that follow are chosen for the same reason: each is a +// place where a wrong config.h answer would show up as a wrong RESULT rather +// than a failure to build. + +#ifdef __linux__ + +extern "C" { +#include +} + +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) { + ++failures; + } +} + +} // namespace + +int main() +{ + // ── 1. glibconfig.h: the type model ────────────────────────────────── + std::printf(" glib %d.%d.%d gsize=%zu gssize=%zu gpointer=%zu\n", + glib_major_version, glib_minor_version, glib_micro_version, + sizeof(gsize), sizeof(gssize), sizeof(gpointer)); + check(glib_major_version == 2 && glib_minor_version == 82, + "the runtime version matches the manifest"); + check(sizeof(gsize) == sizeof(void *) && sizeof(gssize) == sizeof(void *), + "gsize and gssize are pointer-width, as glibconfig.h declares"); + check(sizeof(gint32) == 4 && sizeof(gint64) == 8 && sizeof(gint16) == 2, + "the fixed-width types are the widths they are named for"); + + // G_BYTE_ORDER is written by the generator rather than probed, so it is + // worth checking against something the machine can answer for itself. + const guint32 probe = 0x01020304; + const bool little = *reinterpret_cast(&probe) == 0x04; + std::printf(" G_BYTE_ORDER=%d measured=%s\n", G_BYTE_ORDER, + little ? "little" : "big"); + check((G_BYTE_ORDER == G_LITTLE_ENDIAN) == little, + "G_BYTE_ORDER agrees with what the bytes actually say"); + + // The format strings are the half of glibconfig.h that a compile cannot + // check: `%" G_GSIZE_FORMAT "` with the wrong modifier is undefined + // behaviour that usually prints something plausible. + { + gchar *s = g_strdup_printf("%" G_GSIZE_FORMAT, static_cast(123456789)); + std::printf(" G_GSIZE_FORMAT renders %s\n", s); + check(std::strcmp(s, "123456789") == 0, + "G_GSIZE_FORMAT prints a gsize correctly"); + g_free(s); + } + + // ── 2. gversionmacros.h ────────────────────────────────────────────── + // The generator emits one block per even minor up to the current one. + // Encoding is (major << 16) | (minor << 8) | micro. + std::printf(" GLIB_VERSION_2_82 = 0x%06x\n", GLIB_VERSION_2_82); + check(GLIB_VERSION_2_82 == G_ENCODE_VERSION(2, 82), + "gversionmacros.h defines GLIB_VERSION_2_82 and encodes it correctly"); + check(GLIB_VERSION_2_26 < GLIB_VERSION_2_82, + "…and the older ones too, in order"); + + // ── 3. config.h: USE_SYSTEM_PRINTF ─────────────────────────────────── + // Not directly observable — but %'d (thousands grouping) and the C99 %zu + // are things glib's bundled gnulib printf implements differently from + // glibc's. Rendering a long double in %La exercises the path that made the + // link fail when the wrong printf was selected. + { + gchar *s = g_strdup_printf("%.3f|%05d|%s", 1.5, 42, "x"); + std::printf(" printf path renders %s\n", s); + check(std::strcmp(s, "1.500|00042|x") == 0, + "g_strdup_printf goes through a working printf"); + g_free(s); + } + + // ── 4. glib-visibility.h ───────────────────────────────────────────── + // g_pathbuf_init is GLIB_AVAILABLE_IN_2_76 — a symbol decorated by the + // generated visibility header. Calling it proves the decoration expanded + // to something that exports rather than hides. + { + GPathBuf pb; + g_path_buf_init(&pb); + g_path_buf_push(&pb, "usr"); + g_path_buf_push(&pb, "share"); + gchar *p = g_path_buf_to_path(&pb); + std::printf(" GPathBuf built %s\n", p ? p : "(null)"); + check(p != nullptr && std::strstr(p, "usr/share") != nullptr, + "a GLIB_AVAILABLE_IN_2_76 symbol is reachable"); + g_free(p); + g_path_buf_clear(&pb); + } + + // ── 5. GRegex, i.e. the pcre2 dependency ───────────────────────────── + // The only external library glib links here. \p{Han} needs pcre2 built + // with SUPPORT_UNICODE, which is compat.pcre2's decision — so this checks + // the two packages agree, not merely that one of them built. + { + GError *err = nullptr; + GRegex *re = g_regex_new("^\\p{Han}+$", G_REGEX_DEFAULT, + G_REGEX_MATCH_DEFAULT, &err); + check(re != nullptr, "g_regex_new compiles a Unicode property pattern"); + if (re != nullptr) { + check(g_regex_match(re, "漢字", G_REGEX_MATCH_DEFAULT, nullptr), + "…and it matches Han characters"); + check(!g_regex_match(re, "abc", G_REGEX_MATCH_DEFAULT, nullptr), + "…and not Latin ones"); + g_regex_unref(re); + } else if (err != nullptr) { + std::printf(" %s\n", err->message); + g_error_free(err); + } + } + + // ── 6. Unicode, the tables gunicode.h describes ────────────────────── + check(g_unichar_type(0x6F22) == G_UNICODE_OTHER_LETTER, + "g_unichar_type classifies U+6F22 as a letter"); + // The function is `g_unichar_get_script`, not `g_unichar_script`. + check(g_unichar_get_script(0x6F22) == G_UNICODE_SCRIPT_HAN, + "…and g_unichar_get_script puts it in Han"); + { + gchar *up = g_utf8_strup("straße", -1); + std::printf(" g_utf8_strup(straße) = %s\n", up); + check(up != nullptr && std::strstr(up, "STRASSE") != nullptr, + "g_utf8_strup expands ß to SS, i.e. real case tables"); + g_free(up); + } + + // ── 7. the main loop and GVariant ──────────────────────────────────── + { + GMainContext *ctx = g_main_context_new(); + check(ctx != nullptr, "a GMainContext is created"); + check(!g_main_context_iteration(ctx, FALSE), + "…and iterating it with nothing pending does not block"); + g_main_context_unref(ctx); + } + { + GVariant *v = g_variant_new("(si)", "hello", 42); + g_variant_ref_sink(v); + const gchar *s = nullptr; + gint i = 0; + g_variant_get(v, "(&si)", &s, &i); + std::printf(" GVariant round-trip: %s %d\n", s, i); + check(s != nullptr && std::strcmp(s, "hello") == 0 && i == 42, + "a GVariant tuple survives a serialise/parse round trip"); + g_variant_unref(v); + } + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif diff --git a/tests/examples/gmodule/mcpp.toml b/tests/examples/gmodule/mcpp.toml new file mode 100644 index 0000000..fc144cc --- /dev/null +++ b/tests/examples/gmodule/mcpp.toml @@ -0,0 +1,11 @@ +# gnome.gmodule — dynamic loading, and the generated gmoduleconf.h behind it. +[indices] +gnome = { path = "../../.." } + +[package] +name = "gmodule-tests" +version = "0.1.0" +standard = "c++23" + +[target.'cfg(linux)'.dependencies.gnome] +gmodule = "2.82.5" diff --git a/tests/examples/gmodule/tests/gmodule.cpp b/tests/examples/gmodule/tests/gmodule.cpp new file mode 100644 index 0000000..7c42296 --- /dev/null +++ b/tests/examples/gmodule/tests/gmodule.cpp @@ -0,0 +1,110 @@ +// gmodule — dynamic loading, checked by actually loading something. +// +// WHAT THIS ASSERTS AND WHY +// +// gmodule is two source files, so "did it compile" is nearly free — and nearly +// worthless. The value is in `gmoduleconf.h`, which build.mcpp generates and +// which decides WHICH loader is compiled in. `G_MODULE_IMPL_NONE` produces a +// library that builds, links, and returns "dynamic loading not supported" for +// everything. +// +// So the checks are: does it claim to support loading, and does a load +// actually work. `g_module_open(NULL)` opens the CURRENT EXECUTABLE — the one +// case that needs no test fixture on disk — and a symbol looked up in it must +// be one this binary really has. + +#ifdef __linux__ + +extern "C" { +#include +} + +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-56s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) { + ++failures; + } +} + +} // namespace + +int main() +{ + // ── 1. gmoduleconf.h selected a real loader ────────────────────────── + // G_MODULE_IMPL_NONE here would mean build.mcpp filled the template with + // the "no loader" answer, and everything below would fail in a way that + // reads like a broken system rather than a wrong generated header. + std::printf(" g_module_supported = %d, suffix = %s\n", + g_module_supported(), G_MODULE_SUFFIX); + check(g_module_supported(), "g_module_supported — gmoduleconf.h chose the dl loader"); + check(std::strcmp(G_MODULE_SUFFIX, "so") == 0, + "G_MODULE_SUFFIX is \"so\", from the generated glibconfig.h"); + + // ── 2. a load that needs no fixture ────────────────────────────────── + // NULL means "this executable". It exercises the same dlopen path a plugin + // would, without needing a .so to have been built alongside the test. + GModule *self = g_module_open(nullptr, G_MODULE_BIND_LAZY); + check(self != nullptr, "g_module_open(NULL) opens the running executable"); + if (self != nullptr) { + // ⚠️ NO SYMBOL LOOKUP HERE, and the reason is a property of this + // package rather than a gap in the test. + // + // `dlsym` on the main program sees only the DYNAMIC symbol table, which + // needs `-rdynamic`. And gnome.glib is `kind = "lib"`: its objects are + // merged into the consumer, so there is no libglib-2.0.so in the link + // map either. Both halves of the obvious assertion — a symbol of this + // executable's, or one of glib's — are therefore absent by + // construction, and a test that asserted them would be measuring the + // link line. + // + // What IS observable without a fixture on disk is that the loader + // answers at all, and that it distinguishes present from absent. + gpointer missing = nullptr; + check(!g_module_symbol(self, "mcpp_no_such_symbol_at_all", &missing), + "a name that is not there is reported as absent, not crashed on"); + + std::printf(" g_module_name = %s\n", g_module_name(self)); + check(g_module_close(self), "g_module_close"); + } + + // ── 2b. g_module_build_path — G_MODULE_SUFFIX in use ───────────────── + // The one place the generated glibconfig.h value becomes a STRING a caller + // sees. On Windows this would be "plugin.dll"; getting it wrong would make + // every plugin host look for the wrong filename. + { + gchar *p = g_module_build_path("/opt/plug", "demo"); + std::printf(" g_module_build_path = %s\n", p ? p : "(null)"); + check(p != nullptr && std::strcmp(p, "/opt/plug/libdemo.so") == 0, + "g_module_build_path applies the lib prefix and .so suffix"); + g_free(p); + } + + // ── 3. the error path says something ───────────────────────────────── + // G_MODULE_IMPL_NONE would also reach here, so this is checked after the + // success case rather than instead of it. + GModule *nope = g_module_open("/nonexistent/definitely-not-a-module.so", + G_MODULE_BIND_LAZY); + check(nope == nullptr, "opening a missing module fails"); + const gchar *err = g_module_error(); + std::printf(" g_module_error = %s\n", err ? err : "(null)"); + // G_MODULE_HAVE_DLERROR is one of the four values build.mcpp fills into + // gmoduleconf.h; without it the message would be a generic placeholder + // rather than the loader's own text. + check(err != nullptr && std::strstr(err, "definitely-not-a-module") != nullptr, + "…and dlerror's message names the file — G_MODULE_HAVE_DLERROR is on"); + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif diff --git a/tests/examples/gobject/mcpp.toml b/tests/examples/gobject/mcpp.toml new file mode 100644 index 0000000..dc75d8f --- /dev/null +++ b/tests/examples/gobject/mcpp.toml @@ -0,0 +1,11 @@ +# gnome.gobject — the type system, registered and exercised at run time. +[indices] +gnome = { path = "../../.." } + +[package] +name = "gobject-tests" +version = "0.1.0" +standard = "c++23" + +[target.'cfg(linux)'.dependencies.gnome] +gobject = "2.82.5" diff --git a/tests/examples/gobject/tests/gobject.cpp b/tests/examples/gobject/tests/gobject.cpp new file mode 100644 index 0000000..3268db9 --- /dev/null +++ b/tests/examples/gobject/tests/gobject.cpp @@ -0,0 +1,180 @@ +// gobject — the type system, exercised rather than merely linked. +// +// WHAT THIS ASSERTS AND WHY +// +// GObject is the kind of library where "it linked" proves nothing: GType is a +// runtime registry, so a build that compiled half of it still links and then +// fails the first time something registers a type. Every check below makes the +// library DO something and reads back a value it computed: +// +// g_type_init-era registry a fundamental type's name, from gtype.c +// the GENERATED enum types g_unicode_script_get_type(), which exists only +// because build.mcpp reproduced glib-mkenums +// a derived type registered at run time, with a property +// properties set through GValue and read back +// signals connected, emitted, and observed to have run +// GBinding two objects kept in step, which is gbinding.c +// +// The enum-type check is the one that matters most for this fork: it is the +// only evidence that the mkenums reimplementation produced a REGISTERABLE type +// rather than a header that merely compiles. + +#ifdef __linux__ + +// ⚠️ WRAPPED IN extern "C". glib's headers do have their own G_BEGIN_DECLS, so +// this is belt and braces rather than a necessity — but the generated +// glib-enumtypes.h is produced from a template that emits G_BEGIN_DECLS too, +// and asserting that here is cheaper than discovering it is missing. +extern "C" { +#include +#include +} + +#include +#include + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) { + ++failures; + } +} + +// A minimal derived type, declared the way every GObject library does it. +// Registering it exercises gtype.c, gobject.c, gparam.c and gvalue.c at once. +struct TestThing { + GObject parent; + gint answer; +}; +struct TestThingClass { + GObjectClass parent; +}; + +enum { PROP_0, PROP_ANSWER, N_PROPS }; +GParamSpec *props[N_PROPS] = {}; + +GType test_thing_get_type(void); +G_DEFINE_TYPE(TestThing, test_thing, G_TYPE_OBJECT) + +void test_thing_set_property(GObject *o, guint id, const GValue *v, GParamSpec *p) +{ + if (id == PROP_ANSWER) { + reinterpret_cast(o)->answer = g_value_get_int(v); + } else { + G_OBJECT_WARN_INVALID_PROPERTY_ID(o, id, p); + } +} + +void test_thing_get_property(GObject *o, guint id, GValue *v, GParamSpec *p) +{ + if (id == PROP_ANSWER) { + g_value_set_int(v, reinterpret_cast(o)->answer); + } else { + G_OBJECT_WARN_INVALID_PROPERTY_ID(o, id, p); + } +} + +guint sig_changed = 0; + +void test_thing_class_init(TestThingClass *klass) +{ + GObjectClass *oc = G_OBJECT_CLASS(klass); + oc->set_property = test_thing_set_property; + oc->get_property = test_thing_get_property; + props[PROP_ANSWER] = g_param_spec_int("answer", nullptr, nullptr, + G_MININT, G_MAXINT, 0, + static_cast(G_PARAM_READWRITE)); + g_object_class_install_properties(oc, N_PROPS, props); + + sig_changed = g_signal_new("changed", test_thing_get_type(), G_SIGNAL_RUN_LAST, + 0, nullptr, nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_INT); +} + +void test_thing_init(TestThing *) {} + +int seen = -1; +void on_changed(TestThing *, gint value, gpointer) { seen = value; } + +} // namespace + +int main() +{ + // ── 1. the type registry answers about its own fundamentals ────────── + std::printf(" glib %d.%d.%d\n", glib_major_version, glib_minor_version, + glib_micro_version); + check(glib_major_version == 2 && glib_minor_version == 82, + "glib_*_version report 2.82, the version the manifest declares"); + check(std::strcmp(g_type_name(G_TYPE_INT), "gint") == 0, + "g_type_name(G_TYPE_INT) is \"gint\" — the registry is initialised"); + check(g_type_is_a(G_TYPE_OBJECT, G_TYPE_OBJECT), "g_type_is_a on GObject itself"); + + // ── 2. THE GENERATED ENUM TYPES ────────────────────────────────────── + // These exist only because build.mcpp reproduced glib-mkenums over + // glib/gunicode.h. Registering one and reading a value back is the only + // thing that proves the generator produced working code rather than a + // header that happens to compile. + const GType script = g_unicode_script_get_type(); + check(script != 0, "g_unicode_script_get_type() registered a GType"); + GEnumClass *ec = static_cast(g_type_class_ref(script)); + check(ec != nullptr, "…and its class can be referenced"); + if (ec != nullptr) { + GEnumValue *v = g_enum_get_value(ec, G_UNICODE_SCRIPT_HAN); + std::printf(" G_UNICODE_SCRIPT_HAN = %s / %s\n", + v ? v->value_name : "(null)", v ? v->value_nick : "(null)"); + check(v != nullptr && std::strcmp(v->value_name, "G_UNICODE_SCRIPT_HAN") == 0, + "…and G_UNICODE_SCRIPT_HAN is in it under its own name"); + // The nick is mkenums' derived form: prefix stripped, lower case, + // underscores to hyphens. Getting it right is the fiddly half of the + // generator, so it is asserted rather than assumed. + check(v != nullptr && std::strcmp(v->value_nick, "han") == 0, + "…with the nick mkenums derives, \"han\""); + g_type_class_unref(ec); + } + check(g_unicode_type_get_type() != 0 && g_unicode_break_type_get_type() != 0 + && g_normalize_mode_get_type() != 0, + "all four generated enum types register"); + + // ── 3. a derived type, its property, its signal ────────────────────── + const GType thing = test_thing_get_type(); + check(thing != 0 && g_type_is_a(thing, G_TYPE_OBJECT), + "a derived type registers and is a GObject"); + std::printf(" registered %s\n", g_type_name(thing)); + + GObject *o = static_cast(g_object_new(thing, "answer", 42, nullptr)); + check(o != nullptr, "g_object_new with a construct property"); + + gint got = 0; + g_object_get(o, "answer", &got, nullptr); + std::printf(" answer = %d\n", got); + check(got == 42, "…and g_object_get reads it back through GValue"); + + g_signal_connect(o, "changed", G_CALLBACK(on_changed), nullptr); + g_signal_emit(o, sig_changed, 0, 7); + std::printf(" the handler saw %d\n", seen); + check(seen == 7, "a signal is emitted and the handler runs"); + + // ── 4. GBinding — gbinding.c, and a real use of the property system ── + GObject *b = static_cast(g_object_new(thing, nullptr)); + g_object_bind_property(o, "answer", b, "answer", G_BINDING_DEFAULT); + g_object_set(o, "answer", 99, nullptr); + gint mirrored = 0; + g_object_get(b, "answer", &mirrored, nullptr); + std::printf(" bound object followed to %d\n", mirrored); + check(mirrored == 99, "g_object_bind_property keeps two objects in step"); + + g_object_unref(b); + g_object_unref(o); + check(true, "objects destroyed without a warning"); + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif From 73052f49a546491a9d511cdf6c98d8fae9671133 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 31 Aug 2026 06:24:03 +0800 Subject: [PATCH 5/6] chore: move the three glib packages to a follow-up PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They cannot be verified in THIS PR, and the reason is structural rather than a missing step: gnome.glib depends on compat.pcre2, which this PR is what publishes. An example redirects ONE namespace at a package's index — mcpp registers at most one project index repo — so `gnome` points at this checkout and `compat.pcre2` is looked up in the published index, where it does not exist yet. wlroots now passes here (all twelve assertions, including the pinned pnp.ids resolving ACR to Acer), so this PR is complete on its own. glib, gobject and gmodule 2.82.5 are built, tested on both toolchains, released and mirrored; their descriptors and examples land as soon as compat.pcre2 is on the index. --- mcpp.toml | 3 - pkgs/g/gnome.glib.lua | 93 ------------ pkgs/g/gnome.gmodule.lua | 93 ------------ pkgs/g/gnome.gobject.lua | 93 ------------ tests/examples/glib/mcpp.toml | 11 -- tests/examples/glib/tests/glib.cpp | 180 ----------------------- tests/examples/gmodule/mcpp.toml | 11 -- tests/examples/gmodule/tests/gmodule.cpp | 110 -------------- tests/examples/gobject/mcpp.toml | 11 -- tests/examples/gobject/tests/gobject.cpp | 180 ----------------------- 10 files changed, 785 deletions(-) delete mode 100644 pkgs/g/gnome.glib.lua delete mode 100644 pkgs/g/gnome.gmodule.lua delete mode 100644 pkgs/g/gnome.gobject.lua delete mode 100644 tests/examples/glib/mcpp.toml delete mode 100644 tests/examples/glib/tests/glib.cpp delete mode 100644 tests/examples/gmodule/mcpp.toml delete mode 100644 tests/examples/gmodule/tests/gmodule.cpp delete mode 100644 tests/examples/gobject/mcpp.toml delete mode 100644 tests/examples/gobject/tests/gobject.cpp diff --git a/mcpp.toml b/mcpp.toml index 28e540b..5b33e5c 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -104,9 +104,6 @@ members = [ "tests/examples/wlroots", "tests/examples/pcre2", "tests/examples/fribidi", - "tests/examples/glib", - "tests/examples/gobject", - "tests/examples/gmodule", "tests/examples/libgbm", "tests/examples/libpng", "tests/examples/libwebp", diff --git a/pkgs/g/gnome.glib.lua b/pkgs/g/gnome.glib.lua deleted file mode 100644 index 1aac299..0000000 --- a/pkgs/g/gnome.glib.lua +++ /dev/null @@ -1,93 +0,0 @@ --- gnome.glib — part of GLib 2.82.5. --- --- Upstream ships glib as one source tree producing FOUR separate shared --- libraries, and this index follows that split: `gnome.glib`, --- `gnome.gobject` and `gnome.gmodule` are three packages from one fork, --- because that is what a consumer links. (The fourth, gio, is absent — see --- below.) --- --- ───────────────────────────────────────────────────────────────────────── --- WHY A FORK --- --- Generators, not line count. GLib has six, and `build.mcpp` reimplements all --- of them, so the tree carries no `sh` and no `python`: --- --- gen-visibility-macros.py versions-macros glib/gversionmacros.h --- gen-visibility-macros.py visibility-macros three *-visibility.h --- configure_file glibconfig.h --- configure_file gmodule/gmoduleconf.h --- gobject/glib-mkenums (816 lines of Python) glib-enumtypes.{h,c} --- configure_file config.h --- --- glib-mkenums is reproduced FOR THE ONE INPUT this build points it at — --- `glib/gunicode.h`, four enums — rather than wholesale. It reads upstream's --- `.template` files from the tree so a template change is picked up, and --- exits non-zero if that header stops yielding four enums: a silent drop --- would produce a library missing `g_unicode_script_get_type` and the failure --- would land in a consumer. --- --- https://github.com/mcpplibs/glib --- --- ───────────────────────────────────────────────────────────────────────── --- ⚠️ C++ CONSUMERS MUST WRAP THE INCLUDES --- --- glib's headers carry their own `G_BEGIN_DECLS`, so this is usually fine — --- but the generated `glib-enumtypes.h` comes from a template, and wrapping --- costs nothing: --- --- extern "C" { --- #include --- } --- --- There is no module. glib's API is macro-heavy — `G_DEFINE_TYPE`, --- `G_OBJECT`, `g_signal_connect` are all macros — and macros do not cross a --- module boundary, so an `import` would hand a consumer the declarations and --- withhold the half of the API that makes them usable. Compare --- `wlroots.wlroots`, where the module is the ONLY way in because the headers --- are not valid C++ at all: the shape follows what upstream's headers are, --- not a house style. --- --- ───────────────────────────────────────────────────────────────────────── --- ⚠️ gio IS NOT IN THIS INDEX, AND THAT BLOCKS pango --- --- Two of gio's six generators are `gdbus-codegen`, an 8,351-line Python --- program that turns D-Bus interface XML into GObject skeletons. Seven gio --- sources include its output and two more reference those, so it cannot be --- dropped without changing what gio is, and reimplementing it in --- `build.mcpp` is not proportionate. --- --- pango uses `GListModel`, which lives in gio, so the text-layout line stops --- here. Measured and recorded rather than left as an unexplained gap. --- --- ───────────────────────────────────────────────────────────────────────── --- LINUX ONLY --- --- The generated `glibconfig.h` fixes `G_OS_UNIX`, the POSIX thread --- implementation and the poll constants, and `build.mcpp` refuses to run --- anywhere else rather than emitting a header that is quietly wrong. The --- upstream tree also contains two `COPYING` symlinks, which a Windows --- extraction would not survive — one more reason the descriptor offers --- `linux` alone. -package = { - spec = "1", - namespace = "gnome", - name = "glib", - description = "GLib 2.82.5 — data structures, the main loop, Unicode, GVariant and GRegex", - licenses = {"LGPL-2.1-or-later"}, - repo = "https://github.com/mcpplibs/glib", - type = "package", - - xpm = { - linux = { - ["2.82.5"] = { - url = { - GLOBAL = "https://github.com/mcpplibs/glib/archive/refs/tags/2.82.5.tar.gz", - CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5/glib-2.82.5.tar.gz", - }, - sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", - }, - }, - }, - - mcpp = "*/mcpp/glib/mcpp.toml", -} diff --git a/pkgs/g/gnome.gmodule.lua b/pkgs/g/gnome.gmodule.lua deleted file mode 100644 index e80cb29..0000000 --- a/pkgs/g/gnome.gmodule.lua +++ /dev/null @@ -1,93 +0,0 @@ --- gnome.gmodule — part of GLib 2.82.5. --- --- Upstream ships glib as one source tree producing FOUR separate shared --- libraries, and this index follows that split: `gnome.glib`, --- `gnome.gobject` and `gnome.gmodule` are three packages from one fork, --- because that is what a consumer links. (The fourth, gio, is absent — see --- below.) --- --- ───────────────────────────────────────────────────────────────────────── --- WHY A FORK --- --- Generators, not line count. GLib has six, and `build.mcpp` reimplements all --- of them, so the tree carries no `sh` and no `python`: --- --- gen-visibility-macros.py versions-macros glib/gversionmacros.h --- gen-visibility-macros.py visibility-macros three *-visibility.h --- configure_file glibconfig.h --- configure_file gmodule/gmoduleconf.h --- gobject/glib-mkenums (816 lines of Python) glib-enumtypes.{h,c} --- configure_file config.h --- --- glib-mkenums is reproduced FOR THE ONE INPUT this build points it at — --- `glib/gunicode.h`, four enums — rather than wholesale. It reads upstream's --- `.template` files from the tree so a template change is picked up, and --- exits non-zero if that header stops yielding four enums: a silent drop --- would produce a library missing `g_unicode_script_get_type` and the failure --- would land in a consumer. --- --- https://github.com/mcpplibs/glib --- --- ───────────────────────────────────────────────────────────────────────── --- ⚠️ C++ CONSUMERS MUST WRAP THE INCLUDES --- --- glib's headers carry their own `G_BEGIN_DECLS`, so this is usually fine — --- but the generated `glib-enumtypes.h` comes from a template, and wrapping --- costs nothing: --- --- extern "C" { --- #include --- } --- --- There is no module. glib's API is macro-heavy — `G_DEFINE_TYPE`, --- `G_OBJECT`, `g_signal_connect` are all macros — and macros do not cross a --- module boundary, so an `import` would hand a consumer the declarations and --- withhold the half of the API that makes them usable. Compare --- `wlroots.wlroots`, where the module is the ONLY way in because the headers --- are not valid C++ at all: the shape follows what upstream's headers are, --- not a house style. --- --- ───────────────────────────────────────────────────────────────────────── --- ⚠️ gio IS NOT IN THIS INDEX, AND THAT BLOCKS pango --- --- Two of gio's six generators are `gdbus-codegen`, an 8,351-line Python --- program that turns D-Bus interface XML into GObject skeletons. Seven gio --- sources include its output and two more reference those, so it cannot be --- dropped without changing what gio is, and reimplementing it in --- `build.mcpp` is not proportionate. --- --- pango uses `GListModel`, which lives in gio, so the text-layout line stops --- here. Measured and recorded rather than left as an unexplained gap. --- --- ───────────────────────────────────────────────────────────────────────── --- LINUX ONLY --- --- The generated `glibconfig.h` fixes `G_OS_UNIX`, the POSIX thread --- implementation and the poll constants, and `build.mcpp` refuses to run --- anywhere else rather than emitting a header that is quietly wrong. The --- upstream tree also contains two `COPYING` symlinks, which a Windows --- extraction would not survive — one more reason the descriptor offers --- `linux` alone. -package = { - spec = "1", - namespace = "gnome", - name = "gmodule", - description = "GModule 2.82.5 — portable dynamic module loading over dlopen", - licenses = {"LGPL-2.1-or-later"}, - repo = "https://github.com/mcpplibs/glib", - type = "package", - - xpm = { - linux = { - ["2.82.5"] = { - url = { - GLOBAL = "https://github.com/mcpplibs/glib/archive/refs/tags/2.82.5.tar.gz", - CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5/glib-2.82.5.tar.gz", - }, - sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", - }, - }, - }, - - mcpp = "*/mcpp/gmodule/mcpp.toml", -} diff --git a/pkgs/g/gnome.gobject.lua b/pkgs/g/gnome.gobject.lua deleted file mode 100644 index ef0cc1d..0000000 --- a/pkgs/g/gnome.gobject.lua +++ /dev/null @@ -1,93 +0,0 @@ --- gnome.gobject — part of GLib 2.82.5. --- --- Upstream ships glib as one source tree producing FOUR separate shared --- libraries, and this index follows that split: `gnome.glib`, --- `gnome.gobject` and `gnome.gmodule` are three packages from one fork, --- because that is what a consumer links. (The fourth, gio, is absent — see --- below.) --- --- ───────────────────────────────────────────────────────────────────────── --- WHY A FORK --- --- Generators, not line count. GLib has six, and `build.mcpp` reimplements all --- of them, so the tree carries no `sh` and no `python`: --- --- gen-visibility-macros.py versions-macros glib/gversionmacros.h --- gen-visibility-macros.py visibility-macros three *-visibility.h --- configure_file glibconfig.h --- configure_file gmodule/gmoduleconf.h --- gobject/glib-mkenums (816 lines of Python) glib-enumtypes.{h,c} --- configure_file config.h --- --- glib-mkenums is reproduced FOR THE ONE INPUT this build points it at — --- `glib/gunicode.h`, four enums — rather than wholesale. It reads upstream's --- `.template` files from the tree so a template change is picked up, and --- exits non-zero if that header stops yielding four enums: a silent drop --- would produce a library missing `g_unicode_script_get_type` and the failure --- would land in a consumer. --- --- https://github.com/mcpplibs/glib --- --- ───────────────────────────────────────────────────────────────────────── --- ⚠️ C++ CONSUMERS MUST WRAP THE INCLUDES --- --- glib's headers carry their own `G_BEGIN_DECLS`, so this is usually fine — --- but the generated `glib-enumtypes.h` comes from a template, and wrapping --- costs nothing: --- --- extern "C" { --- #include --- } --- --- There is no module. glib's API is macro-heavy — `G_DEFINE_TYPE`, --- `G_OBJECT`, `g_signal_connect` are all macros — and macros do not cross a --- module boundary, so an `import` would hand a consumer the declarations and --- withhold the half of the API that makes them usable. Compare --- `wlroots.wlroots`, where the module is the ONLY way in because the headers --- are not valid C++ at all: the shape follows what upstream's headers are, --- not a house style. --- --- ───────────────────────────────────────────────────────────────────────── --- ⚠️ gio IS NOT IN THIS INDEX, AND THAT BLOCKS pango --- --- Two of gio's six generators are `gdbus-codegen`, an 8,351-line Python --- program that turns D-Bus interface XML into GObject skeletons. Seven gio --- sources include its output and two more reference those, so it cannot be --- dropped without changing what gio is, and reimplementing it in --- `build.mcpp` is not proportionate. --- --- pango uses `GListModel`, which lives in gio, so the text-layout line stops --- here. Measured and recorded rather than left as an unexplained gap. --- --- ───────────────────────────────────────────────────────────────────────── --- LINUX ONLY --- --- The generated `glibconfig.h` fixes `G_OS_UNIX`, the POSIX thread --- implementation and the poll constants, and `build.mcpp` refuses to run --- anywhere else rather than emitting a header that is quietly wrong. The --- upstream tree also contains two `COPYING` symlinks, which a Windows --- extraction would not survive — one more reason the descriptor offers --- `linux` alone. -package = { - spec = "1", - namespace = "gnome", - name = "gobject", - description = "GObject 2.82.5 — the GLib type system: GType, signals, properties, closures and GValue", - licenses = {"LGPL-2.1-or-later"}, - repo = "https://github.com/mcpplibs/glib", - type = "package", - - xpm = { - linux = { - ["2.82.5"] = { - url = { - GLOBAL = "https://github.com/mcpplibs/glib/archive/refs/tags/2.82.5.tar.gz", - CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5/glib-2.82.5.tar.gz", - }, - sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", - }, - }, - }, - - mcpp = "*/mcpp/gobject/mcpp.toml", -} diff --git a/tests/examples/glib/mcpp.toml b/tests/examples/glib/mcpp.toml deleted file mode 100644 index 63854f9..0000000 --- a/tests/examples/glib/mcpp.toml +++ /dev/null @@ -1,11 +0,0 @@ -# gnome.glib — the generated headers, and the subsystems that depend on them. -[indices] -gnome = { path = "../../.." } - -[package] -name = "glib-tests" -version = "0.1.0" -standard = "c++23" - -[target.'cfg(linux)'.dependencies.gnome] -glib = "2.82.5" diff --git a/tests/examples/glib/tests/glib.cpp b/tests/examples/glib/tests/glib.cpp deleted file mode 100644 index ddd2cb2..0000000 --- a/tests/examples/glib/tests/glib.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// glib — the generated headers, and the subsystems that depend on them. -// -// WHAT THIS ASSERTS AND WHY -// -// glib is large and mostly self-evident: if GHashTable were broken the build -// would not have got here. What is NOT self-evident in a fork is whether the -// five generated headers say the right things, because every one of them was -// written out by hand-reimplemented generators. A wrong answer in glibconfig.h -// does not fail to compile — it changes the width of gsize, or the byte order, -// or which printf glib calls. -// -// So each check reads back a value that a generator decided: -// -// glibconfig.h gsize/gssize widths, G_BYTE_ORDER, the format strings -// gversionmacros.h GLIB_VERSION_2_82 exists and encodes 2.82 -// config.h USE_SYSTEM_PRINTF, via a format only glibc gets right -// glib-visibility.h a GLIB_AVAILABLE_IN_2_x symbol is actually exported -// pcre2 wiring GRegex, which is the one external dependency -// -// The subsystem checks that follow are chosen for the same reason: each is a -// place where a wrong config.h answer would show up as a wrong RESULT rather -// than a failure to build. - -#ifdef __linux__ - -extern "C" { -#include -} - -#include -#include - -namespace { - -int failures = 0; - -void check(bool ok, const char *what) -{ - std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); - if (!ok) { - ++failures; - } -} - -} // namespace - -int main() -{ - // ── 1. glibconfig.h: the type model ────────────────────────────────── - std::printf(" glib %d.%d.%d gsize=%zu gssize=%zu gpointer=%zu\n", - glib_major_version, glib_minor_version, glib_micro_version, - sizeof(gsize), sizeof(gssize), sizeof(gpointer)); - check(glib_major_version == 2 && glib_minor_version == 82, - "the runtime version matches the manifest"); - check(sizeof(gsize) == sizeof(void *) && sizeof(gssize) == sizeof(void *), - "gsize and gssize are pointer-width, as glibconfig.h declares"); - check(sizeof(gint32) == 4 && sizeof(gint64) == 8 && sizeof(gint16) == 2, - "the fixed-width types are the widths they are named for"); - - // G_BYTE_ORDER is written by the generator rather than probed, so it is - // worth checking against something the machine can answer for itself. - const guint32 probe = 0x01020304; - const bool little = *reinterpret_cast(&probe) == 0x04; - std::printf(" G_BYTE_ORDER=%d measured=%s\n", G_BYTE_ORDER, - little ? "little" : "big"); - check((G_BYTE_ORDER == G_LITTLE_ENDIAN) == little, - "G_BYTE_ORDER agrees with what the bytes actually say"); - - // The format strings are the half of glibconfig.h that a compile cannot - // check: `%" G_GSIZE_FORMAT "` with the wrong modifier is undefined - // behaviour that usually prints something plausible. - { - gchar *s = g_strdup_printf("%" G_GSIZE_FORMAT, static_cast(123456789)); - std::printf(" G_GSIZE_FORMAT renders %s\n", s); - check(std::strcmp(s, "123456789") == 0, - "G_GSIZE_FORMAT prints a gsize correctly"); - g_free(s); - } - - // ── 2. gversionmacros.h ────────────────────────────────────────────── - // The generator emits one block per even minor up to the current one. - // Encoding is (major << 16) | (minor << 8) | micro. - std::printf(" GLIB_VERSION_2_82 = 0x%06x\n", GLIB_VERSION_2_82); - check(GLIB_VERSION_2_82 == G_ENCODE_VERSION(2, 82), - "gversionmacros.h defines GLIB_VERSION_2_82 and encodes it correctly"); - check(GLIB_VERSION_2_26 < GLIB_VERSION_2_82, - "…and the older ones too, in order"); - - // ── 3. config.h: USE_SYSTEM_PRINTF ─────────────────────────────────── - // Not directly observable — but %'d (thousands grouping) and the C99 %zu - // are things glib's bundled gnulib printf implements differently from - // glibc's. Rendering a long double in %La exercises the path that made the - // link fail when the wrong printf was selected. - { - gchar *s = g_strdup_printf("%.3f|%05d|%s", 1.5, 42, "x"); - std::printf(" printf path renders %s\n", s); - check(std::strcmp(s, "1.500|00042|x") == 0, - "g_strdup_printf goes through a working printf"); - g_free(s); - } - - // ── 4. glib-visibility.h ───────────────────────────────────────────── - // g_pathbuf_init is GLIB_AVAILABLE_IN_2_76 — a symbol decorated by the - // generated visibility header. Calling it proves the decoration expanded - // to something that exports rather than hides. - { - GPathBuf pb; - g_path_buf_init(&pb); - g_path_buf_push(&pb, "usr"); - g_path_buf_push(&pb, "share"); - gchar *p = g_path_buf_to_path(&pb); - std::printf(" GPathBuf built %s\n", p ? p : "(null)"); - check(p != nullptr && std::strstr(p, "usr/share") != nullptr, - "a GLIB_AVAILABLE_IN_2_76 symbol is reachable"); - g_free(p); - g_path_buf_clear(&pb); - } - - // ── 5. GRegex, i.e. the pcre2 dependency ───────────────────────────── - // The only external library glib links here. \p{Han} needs pcre2 built - // with SUPPORT_UNICODE, which is compat.pcre2's decision — so this checks - // the two packages agree, not merely that one of them built. - { - GError *err = nullptr; - GRegex *re = g_regex_new("^\\p{Han}+$", G_REGEX_DEFAULT, - G_REGEX_MATCH_DEFAULT, &err); - check(re != nullptr, "g_regex_new compiles a Unicode property pattern"); - if (re != nullptr) { - check(g_regex_match(re, "漢字", G_REGEX_MATCH_DEFAULT, nullptr), - "…and it matches Han characters"); - check(!g_regex_match(re, "abc", G_REGEX_MATCH_DEFAULT, nullptr), - "…and not Latin ones"); - g_regex_unref(re); - } else if (err != nullptr) { - std::printf(" %s\n", err->message); - g_error_free(err); - } - } - - // ── 6. Unicode, the tables gunicode.h describes ────────────────────── - check(g_unichar_type(0x6F22) == G_UNICODE_OTHER_LETTER, - "g_unichar_type classifies U+6F22 as a letter"); - // The function is `g_unichar_get_script`, not `g_unichar_script`. - check(g_unichar_get_script(0x6F22) == G_UNICODE_SCRIPT_HAN, - "…and g_unichar_get_script puts it in Han"); - { - gchar *up = g_utf8_strup("straße", -1); - std::printf(" g_utf8_strup(straße) = %s\n", up); - check(up != nullptr && std::strstr(up, "STRASSE") != nullptr, - "g_utf8_strup expands ß to SS, i.e. real case tables"); - g_free(up); - } - - // ── 7. the main loop and GVariant ──────────────────────────────────── - { - GMainContext *ctx = g_main_context_new(); - check(ctx != nullptr, "a GMainContext is created"); - check(!g_main_context_iteration(ctx, FALSE), - "…and iterating it with nothing pending does not block"); - g_main_context_unref(ctx); - } - { - GVariant *v = g_variant_new("(si)", "hello", 42); - g_variant_ref_sink(v); - const gchar *s = nullptr; - gint i = 0; - g_variant_get(v, "(&si)", &s, &i); - std::printf(" GVariant round-trip: %s %d\n", s, i); - check(s != nullptr && std::strcmp(s, "hello") == 0 && i == 42, - "a GVariant tuple survives a serialise/parse round trip"); - g_variant_unref(v); - } - - std::printf("\n%d check(s) failed\n", failures); - return failures == 0 ? 0 : 1; -} - -#else -int main() { return 0; } -#endif diff --git a/tests/examples/gmodule/mcpp.toml b/tests/examples/gmodule/mcpp.toml deleted file mode 100644 index fc144cc..0000000 --- a/tests/examples/gmodule/mcpp.toml +++ /dev/null @@ -1,11 +0,0 @@ -# gnome.gmodule — dynamic loading, and the generated gmoduleconf.h behind it. -[indices] -gnome = { path = "../../.." } - -[package] -name = "gmodule-tests" -version = "0.1.0" -standard = "c++23" - -[target.'cfg(linux)'.dependencies.gnome] -gmodule = "2.82.5" diff --git a/tests/examples/gmodule/tests/gmodule.cpp b/tests/examples/gmodule/tests/gmodule.cpp deleted file mode 100644 index 7c42296..0000000 --- a/tests/examples/gmodule/tests/gmodule.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// gmodule — dynamic loading, checked by actually loading something. -// -// WHAT THIS ASSERTS AND WHY -// -// gmodule is two source files, so "did it compile" is nearly free — and nearly -// worthless. The value is in `gmoduleconf.h`, which build.mcpp generates and -// which decides WHICH loader is compiled in. `G_MODULE_IMPL_NONE` produces a -// library that builds, links, and returns "dynamic loading not supported" for -// everything. -// -// So the checks are: does it claim to support loading, and does a load -// actually work. `g_module_open(NULL)` opens the CURRENT EXECUTABLE — the one -// case that needs no test fixture on disk — and a symbol looked up in it must -// be one this binary really has. - -#ifdef __linux__ - -extern "C" { -#include -} - -#include -#include - -namespace { - -int failures = 0; - -void check(bool ok, const char *what) -{ - std::printf("%-56s %s\n", what, ok ? "ok" : "FAILED"); - if (!ok) { - ++failures; - } -} - -} // namespace - -int main() -{ - // ── 1. gmoduleconf.h selected a real loader ────────────────────────── - // G_MODULE_IMPL_NONE here would mean build.mcpp filled the template with - // the "no loader" answer, and everything below would fail in a way that - // reads like a broken system rather than a wrong generated header. - std::printf(" g_module_supported = %d, suffix = %s\n", - g_module_supported(), G_MODULE_SUFFIX); - check(g_module_supported(), "g_module_supported — gmoduleconf.h chose the dl loader"); - check(std::strcmp(G_MODULE_SUFFIX, "so") == 0, - "G_MODULE_SUFFIX is \"so\", from the generated glibconfig.h"); - - // ── 2. a load that needs no fixture ────────────────────────────────── - // NULL means "this executable". It exercises the same dlopen path a plugin - // would, without needing a .so to have been built alongside the test. - GModule *self = g_module_open(nullptr, G_MODULE_BIND_LAZY); - check(self != nullptr, "g_module_open(NULL) opens the running executable"); - if (self != nullptr) { - // ⚠️ NO SYMBOL LOOKUP HERE, and the reason is a property of this - // package rather than a gap in the test. - // - // `dlsym` on the main program sees only the DYNAMIC symbol table, which - // needs `-rdynamic`. And gnome.glib is `kind = "lib"`: its objects are - // merged into the consumer, so there is no libglib-2.0.so in the link - // map either. Both halves of the obvious assertion — a symbol of this - // executable's, or one of glib's — are therefore absent by - // construction, and a test that asserted them would be measuring the - // link line. - // - // What IS observable without a fixture on disk is that the loader - // answers at all, and that it distinguishes present from absent. - gpointer missing = nullptr; - check(!g_module_symbol(self, "mcpp_no_such_symbol_at_all", &missing), - "a name that is not there is reported as absent, not crashed on"); - - std::printf(" g_module_name = %s\n", g_module_name(self)); - check(g_module_close(self), "g_module_close"); - } - - // ── 2b. g_module_build_path — G_MODULE_SUFFIX in use ───────────────── - // The one place the generated glibconfig.h value becomes a STRING a caller - // sees. On Windows this would be "plugin.dll"; getting it wrong would make - // every plugin host look for the wrong filename. - { - gchar *p = g_module_build_path("/opt/plug", "demo"); - std::printf(" g_module_build_path = %s\n", p ? p : "(null)"); - check(p != nullptr && std::strcmp(p, "/opt/plug/libdemo.so") == 0, - "g_module_build_path applies the lib prefix and .so suffix"); - g_free(p); - } - - // ── 3. the error path says something ───────────────────────────────── - // G_MODULE_IMPL_NONE would also reach here, so this is checked after the - // success case rather than instead of it. - GModule *nope = g_module_open("/nonexistent/definitely-not-a-module.so", - G_MODULE_BIND_LAZY); - check(nope == nullptr, "opening a missing module fails"); - const gchar *err = g_module_error(); - std::printf(" g_module_error = %s\n", err ? err : "(null)"); - // G_MODULE_HAVE_DLERROR is one of the four values build.mcpp fills into - // gmoduleconf.h; without it the message would be a generic placeholder - // rather than the loader's own text. - check(err != nullptr && std::strstr(err, "definitely-not-a-module") != nullptr, - "…and dlerror's message names the file — G_MODULE_HAVE_DLERROR is on"); - - std::printf("\n%d check(s) failed\n", failures); - return failures == 0 ? 0 : 1; -} - -#else -int main() { return 0; } -#endif diff --git a/tests/examples/gobject/mcpp.toml b/tests/examples/gobject/mcpp.toml deleted file mode 100644 index dc75d8f..0000000 --- a/tests/examples/gobject/mcpp.toml +++ /dev/null @@ -1,11 +0,0 @@ -# gnome.gobject — the type system, registered and exercised at run time. -[indices] -gnome = { path = "../../.." } - -[package] -name = "gobject-tests" -version = "0.1.0" -standard = "c++23" - -[target.'cfg(linux)'.dependencies.gnome] -gobject = "2.82.5" diff --git a/tests/examples/gobject/tests/gobject.cpp b/tests/examples/gobject/tests/gobject.cpp deleted file mode 100644 index 3268db9..0000000 --- a/tests/examples/gobject/tests/gobject.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// gobject — the type system, exercised rather than merely linked. -// -// WHAT THIS ASSERTS AND WHY -// -// GObject is the kind of library where "it linked" proves nothing: GType is a -// runtime registry, so a build that compiled half of it still links and then -// fails the first time something registers a type. Every check below makes the -// library DO something and reads back a value it computed: -// -// g_type_init-era registry a fundamental type's name, from gtype.c -// the GENERATED enum types g_unicode_script_get_type(), which exists only -// because build.mcpp reproduced glib-mkenums -// a derived type registered at run time, with a property -// properties set through GValue and read back -// signals connected, emitted, and observed to have run -// GBinding two objects kept in step, which is gbinding.c -// -// The enum-type check is the one that matters most for this fork: it is the -// only evidence that the mkenums reimplementation produced a REGISTERABLE type -// rather than a header that merely compiles. - -#ifdef __linux__ - -// ⚠️ WRAPPED IN extern "C". glib's headers do have their own G_BEGIN_DECLS, so -// this is belt and braces rather than a necessity — but the generated -// glib-enumtypes.h is produced from a template that emits G_BEGIN_DECLS too, -// and asserting that here is cheaper than discovering it is missing. -extern "C" { -#include -#include -} - -#include -#include - -namespace { - -int failures = 0; - -void check(bool ok, const char *what) -{ - std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); - if (!ok) { - ++failures; - } -} - -// A minimal derived type, declared the way every GObject library does it. -// Registering it exercises gtype.c, gobject.c, gparam.c and gvalue.c at once. -struct TestThing { - GObject parent; - gint answer; -}; -struct TestThingClass { - GObjectClass parent; -}; - -enum { PROP_0, PROP_ANSWER, N_PROPS }; -GParamSpec *props[N_PROPS] = {}; - -GType test_thing_get_type(void); -G_DEFINE_TYPE(TestThing, test_thing, G_TYPE_OBJECT) - -void test_thing_set_property(GObject *o, guint id, const GValue *v, GParamSpec *p) -{ - if (id == PROP_ANSWER) { - reinterpret_cast(o)->answer = g_value_get_int(v); - } else { - G_OBJECT_WARN_INVALID_PROPERTY_ID(o, id, p); - } -} - -void test_thing_get_property(GObject *o, guint id, GValue *v, GParamSpec *p) -{ - if (id == PROP_ANSWER) { - g_value_set_int(v, reinterpret_cast(o)->answer); - } else { - G_OBJECT_WARN_INVALID_PROPERTY_ID(o, id, p); - } -} - -guint sig_changed = 0; - -void test_thing_class_init(TestThingClass *klass) -{ - GObjectClass *oc = G_OBJECT_CLASS(klass); - oc->set_property = test_thing_set_property; - oc->get_property = test_thing_get_property; - props[PROP_ANSWER] = g_param_spec_int("answer", nullptr, nullptr, - G_MININT, G_MAXINT, 0, - static_cast(G_PARAM_READWRITE)); - g_object_class_install_properties(oc, N_PROPS, props); - - sig_changed = g_signal_new("changed", test_thing_get_type(), G_SIGNAL_RUN_LAST, - 0, nullptr, nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_INT); -} - -void test_thing_init(TestThing *) {} - -int seen = -1; -void on_changed(TestThing *, gint value, gpointer) { seen = value; } - -} // namespace - -int main() -{ - // ── 1. the type registry answers about its own fundamentals ────────── - std::printf(" glib %d.%d.%d\n", glib_major_version, glib_minor_version, - glib_micro_version); - check(glib_major_version == 2 && glib_minor_version == 82, - "glib_*_version report 2.82, the version the manifest declares"); - check(std::strcmp(g_type_name(G_TYPE_INT), "gint") == 0, - "g_type_name(G_TYPE_INT) is \"gint\" — the registry is initialised"); - check(g_type_is_a(G_TYPE_OBJECT, G_TYPE_OBJECT), "g_type_is_a on GObject itself"); - - // ── 2. THE GENERATED ENUM TYPES ────────────────────────────────────── - // These exist only because build.mcpp reproduced glib-mkenums over - // glib/gunicode.h. Registering one and reading a value back is the only - // thing that proves the generator produced working code rather than a - // header that happens to compile. - const GType script = g_unicode_script_get_type(); - check(script != 0, "g_unicode_script_get_type() registered a GType"); - GEnumClass *ec = static_cast(g_type_class_ref(script)); - check(ec != nullptr, "…and its class can be referenced"); - if (ec != nullptr) { - GEnumValue *v = g_enum_get_value(ec, G_UNICODE_SCRIPT_HAN); - std::printf(" G_UNICODE_SCRIPT_HAN = %s / %s\n", - v ? v->value_name : "(null)", v ? v->value_nick : "(null)"); - check(v != nullptr && std::strcmp(v->value_name, "G_UNICODE_SCRIPT_HAN") == 0, - "…and G_UNICODE_SCRIPT_HAN is in it under its own name"); - // The nick is mkenums' derived form: prefix stripped, lower case, - // underscores to hyphens. Getting it right is the fiddly half of the - // generator, so it is asserted rather than assumed. - check(v != nullptr && std::strcmp(v->value_nick, "han") == 0, - "…with the nick mkenums derives, \"han\""); - g_type_class_unref(ec); - } - check(g_unicode_type_get_type() != 0 && g_unicode_break_type_get_type() != 0 - && g_normalize_mode_get_type() != 0, - "all four generated enum types register"); - - // ── 3. a derived type, its property, its signal ────────────────────── - const GType thing = test_thing_get_type(); - check(thing != 0 && g_type_is_a(thing, G_TYPE_OBJECT), - "a derived type registers and is a GObject"); - std::printf(" registered %s\n", g_type_name(thing)); - - GObject *o = static_cast(g_object_new(thing, "answer", 42, nullptr)); - check(o != nullptr, "g_object_new with a construct property"); - - gint got = 0; - g_object_get(o, "answer", &got, nullptr); - std::printf(" answer = %d\n", got); - check(got == 42, "…and g_object_get reads it back through GValue"); - - g_signal_connect(o, "changed", G_CALLBACK(on_changed), nullptr); - g_signal_emit(o, sig_changed, 0, 7); - std::printf(" the handler saw %d\n", seen); - check(seen == 7, "a signal is emitted and the handler runs"); - - // ── 4. GBinding — gbinding.c, and a real use of the property system ── - GObject *b = static_cast(g_object_new(thing, nullptr)); - g_object_bind_property(o, "answer", b, "answer", G_BINDING_DEFAULT); - g_object_set(o, "answer", 99, nullptr); - gint mirrored = 0; - g_object_get(b, "answer", &mirrored, nullptr); - std::printf(" bound object followed to %d\n", mirrored); - check(mirrored == 99, "g_object_bind_property keeps two objects in step"); - - g_object_unref(b); - g_object_unref(o); - check(true, "objects destroyed without a warning"); - - std::printf("\n%d check(s) failed\n", failures); - return failures == 0 ? 0 : 1; -} - -#else -int main() { return 0; } -#endif From 487353e61be2978b348d035508cc9553cda65aa8 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 31 Aug 2026 06:29:16 +0800 Subject: [PATCH 6/6] fix(tests): the enum-header assertions belong INSIDE the linux guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS and Windows failed while both Linux legs passed, and the cause was in this PR rather than in any package: the 1.49.1 enum-header includes were appended at the END of the file, past the `#endif` that closes `#ifdef __linux__`. So they compiled on platforms where the dependency is never fetched — the example gates it with `cfg(linux)` — and the scan failed before anything was built. Appending to a file is not the same as adding to it. Moved above the `#else`, where the rest of the test lives; verified it still passes on Linux. --- .../wayland-protocols/tests/wayland-protocols.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/examples/wayland-protocols/tests/wayland-protocols.cpp b/tests/examples/wayland-protocols/tests/wayland-protocols.cpp index 0e037f9..b8184a8 100644 --- a/tests/examples/wayland-protocols/tests/wayland-protocols.cpp +++ b/tests/examples/wayland-protocols/tests/wayland-protocols.cpp @@ -93,12 +93,6 @@ int main() return failures == 0 ? 0 : 1; } -#else - -int main() { return 0; } - -#endif - // ── 1.49.1: the enum headers upstream installs ─────────────────────────────── // // `wayland-scanner enum-header` output, which this fork did not generate before @@ -117,3 +111,9 @@ static_assert(XDG_TOPLEVEL_STATE_MAXIMIZED == 1, "xdg-shell-enum.h did not come from the scanner"); static_assert(ZWP_TABLET_TOOL_V2_TYPE_PEN == 0x140, "tablet-v2-enum.h did not come from the scanner"); + +#else + +int main() { return 0; } + +#endif