From 288a4fee7dc3c3171903f7061fcd17d493165db1 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 31 Aug 2026 06:35:55 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(glib):=20GLib=202.82.5=20as=20three=20?= =?UTF-8?q?packages=20=E2=80=94=20glib,=20gobject,=20gmodule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream ships one source tree producing four separate shared libraries, and this index follows that split, because that is what a consumer links. WHY A FORK: six generators, 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 rather than in CI. NO MODULE, DELIBERATELY. 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 that makes them usable. Compare wlroots.wlroots in this same index, 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. WHAT THE TESTS ASSERT. Not "it linked" — each reads back a value a generator decided: G_BYTE_ORDER against what the bytes actually say; G_GSIZE_FORMAT by printing a gsize; GLIB_VERSION_2_82 against G_ENCODE_VERSION; g_utf8_strup of "straße" giving STRASSE, i.e. real case tables; \p{Han} through GRegex, which is the one place this package and compat.pcre2 have to agree; and g_unicode_script_get_type() registering a GType whose value nick is "han" — the fiddly half of mkenums, and the only evidence the generator produced REGISTERABLE code rather than a header that compiles. gobject registers a derived type at run time with a property and a signal, emits it, and binds two objects together. gmodule opens a module and checks what its generated header decided — but does NOT dlsym into itself, because glib is kind = "lib" and there is no shared object in the link map to find one in; that assertion would be measuring the link line. ⚠️ gio IS NOT HERE, AND THAT BLOCKS pango. Two of gio's six generators are gdbus-codegen, an 8,351-line Python program turning D-Bus interface XML into GObject skeletons; seven gio sources include its output and two more reference those. pango uses GListModel, which lives in gio. Every other pango dependency — harfbuzz, fribidi, fontconfig, cairo — is already in this index. Measured and recorded rather than left as an unexplained gap. Fork CI green on both toolchains; the three examples pass here against the published 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 | 184 ++++++++++++++++++++++ tests/examples/gmodule/mcpp.toml | 11 ++ tests/examples/gmodule/tests/gmodule.cpp | 114 ++++++++++++++ tests/examples/gobject/mcpp.toml | 11 ++ tests/examples/gobject/tests/gobject.cpp | 191 +++++++++++++++++++++++ 10 files changed, 804 insertions(+) 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/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/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..f9bd9b6 --- /dev/null +++ b/tests/examples/glib/tests/glib.cpp @@ -0,0 +1,184 @@ +// 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__ + +// ⚠️ NO extern "C" WRAPPER, and adding one BREAKS THIS UNDER libc++: glib +// decorates every header with G_BEGIN_DECLS (which IS `extern "C" {`), and +// glib.h pulls , which libc++ routes through — templates +// inside an extern "C" block. `templates must have C++ linkage`, dozens of +// times, against a header this test never names. libstdc++ does not, so gcc is +// green and llvm is a wall. Wrap a C header only if it has none of its own. +#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..7f3a528 --- /dev/null +++ b/tests/examples/gmodule/tests/gmodule.cpp @@ -0,0 +1,114 @@ +// 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__ + +// ⚠️ NO extern "C" WRAPPER, and adding one BREAKS THIS UNDER libc++: glib +// decorates every header with G_BEGIN_DECLS (which IS `extern "C" {`), and +// glib.h pulls , which libc++ routes through — templates +// inside an extern "C" block. `templates must have C++ linkage`, dozens of +// times, against a header this test never names. libstdc++ does not, so gcc is +// green and llvm is a wall. Wrap a C header only if it has none of its own. +#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..bcfcea8 --- /dev/null +++ b/tests/examples/gobject/tests/gobject.cpp @@ -0,0 +1,191 @@ +// 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__ + +// ⚠️ NO extern "C" WRAPPER, and adding one BREAKS THIS UNDER libc++. +// +// glib decorates every header with G_BEGIN_DECLS/G_END_DECLS, which IS +// `extern "C" {`, so a wrapper is redundant. It is also harmful: glib.h pulls +// and , and libc++ routes those through +// /, which define TEMPLATES. Inside an extern "C" block that +// is +// +// __type_traits/add_cv_quals.h:20: error: templates must have C++ linkage +// +// reported dozens of times against a standard-library header this test never +// names. libstdc++ does not route them that way, so the gcc leg is green and +// the llvm leg is a wall of errors — the same asymmetry that caught a missing +// in the cairo fork. +// +// The rule: wrap a C header ONLY if it has no extern "C" of its own. +// compat.libseat and freedesktop.libdisplay-info need it; glib does not. +#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 38d083282730ece2345014598f7a8a623714e21d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 31 Aug 2026 07:03:01 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(glib):=20re-cut=202.82.5=20=E2=80=94=20?= =?UTF-8?q?gmoduleconf.h=20goes=20flat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI failed on a fresh extraction with gmodule.c:52: fatal error: gmoduleconf.h: No such file or directory while every warm tree passed — the signature of the trap this fork already documents for `include/` itself. mcpp builds the compiler command line from `include_dirs` BEFORE running the build program and SILENTLY DROPS an entry naming a directory that does not exist yet. `include/.gitkeep` makes `include/` exist; it does not make `include/gmodule/` exist, and that was the entry gmodule needed. Writing gmoduleconf.h flat removes the entry and the problem together. Fork CI green on both toolchains; the three examples pass here against the re-cut release. CN container moves to 2.82.5-1: gitcode will not replace an asset of the same name. --- pkgs/g/gnome.glib.lua | 10 ++++++++-- pkgs/g/gnome.gmodule.lua | 10 ++++++++-- pkgs/g/gnome.gobject.lua | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/pkgs/g/gnome.glib.lua b/pkgs/g/gnome.glib.lua index 1aac299..6aad327 100644 --- a/pkgs/g/gnome.glib.lua +++ b/pkgs/g/gnome.glib.lua @@ -82,9 +82,15 @@ package = { ["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", + -- ⚠️ The container tag is `2.82.5-1`, not `2.82.5`. The fork's + -- tag was re-cut once while this descriptor was still + -- unpublished — safe only because nothing had extracted it + -- yet — and gitcode refuses to REPLACE an asset of the same + -- name in an existing release. Verified: this URL's sha256 + -- equals the GLOBAL tarball's. + CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5-1/glib-2.82.5.tar.gz", }, - sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", + sha256 = "98118dacf3ebc9d5aefba340e9248385f1643b76ca672b864c37a3e4fb71caf6", }, }, }, diff --git a/pkgs/g/gnome.gmodule.lua b/pkgs/g/gnome.gmodule.lua index e80cb29..913a351 100644 --- a/pkgs/g/gnome.gmodule.lua +++ b/pkgs/g/gnome.gmodule.lua @@ -82,9 +82,15 @@ package = { ["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", + -- ⚠️ The container tag is `2.82.5-1`, not `2.82.5`. The fork's + -- tag was re-cut once while this descriptor was still + -- unpublished — safe only because nothing had extracted it + -- yet — and gitcode refuses to REPLACE an asset of the same + -- name in an existing release. Verified: this URL's sha256 + -- equals the GLOBAL tarball's. + CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5-1/glib-2.82.5.tar.gz", }, - sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", + sha256 = "98118dacf3ebc9d5aefba340e9248385f1643b76ca672b864c37a3e4fb71caf6", }, }, }, diff --git a/pkgs/g/gnome.gobject.lua b/pkgs/g/gnome.gobject.lua index ef0cc1d..49500ce 100644 --- a/pkgs/g/gnome.gobject.lua +++ b/pkgs/g/gnome.gobject.lua @@ -82,9 +82,15 @@ package = { ["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", + -- ⚠️ The container tag is `2.82.5-1`, not `2.82.5`. The fork's + -- tag was re-cut once while this descriptor was still + -- unpublished — safe only because nothing had extracted it + -- yet — and gitcode refuses to REPLACE an asset of the same + -- name in an existing release. Verified: this URL's sha256 + -- equals the GLOBAL tarball's. + CN = "https://gitcode.com/mcpp-res/glib/releases/download/2.82.5-1/glib-2.82.5.tar.gz", }, - sha256 = "ea18347c494e6df76cfce4838473b0d53992e670499d5714342829b22c92c308", + sha256 = "98118dacf3ebc9d5aefba340e9248385f1643b76ca672b864c37a3e4fb71caf6", }, }, },