GLib 2.82.5 as four mcpp packages, with upstream/ untouched and everything
this fork adds under mcpp/.
[dependencies]
gnome.gobject = "2.82.5" # GType, signals, properties, GValue
gnome.gio = "2.82.5" # files, streams, sockets, D-Bus, GListModel
gnome.gmodule = "2.82.5" # dynamic module loading
# gnome.glib arrives transitively — do NOT name it; see below| module | exports |
|---|---|
gnome.glib |
2,732 |
gnome.gobject |
495 · re-exports gnome.glib |
gnome.gmodule |
20 · re-exports gnome.glib |
gnome.gio |
2,850 · re-exports all three |
The re-exports are not a convenience: glib is a workspace path dependency,
so a consumer that also named it would get requested as both a version dep and a path dep. import gnome.gio; has to be as complete as #include <gio/gio.h>,
and it is.
Two ways to consume it, and you pick one:
// ── the module route ──────────────────────────────────────────────────
import gnome.gio; // re-exports gnome.glib, gnome.gobject, gnome.gmodule
GFile *f = g_file_new_for_path("/etc/hostname");// ── the header route ──────────────────────────────────────────────────
// No extern "C": glib decorates its own headers with G_BEGIN_DECLS, and
// wrapping them breaks under libc++ (glib.h pulls <stdlib.h>, which libc++
// routes through <cstdlib> — templates inside an extern "C" block).
#include <gio/gio.h>
GFile *f = g_file_new_for_path("/etc/hostname");
GListStore *s = g_list_store_new(G_TYPE_FILE); // a MACRO — header route onlyA TU that imports the module and textually includes a glib header reaches
<time.h> twice — once through the module's global fragment, once directly —
and the same struct tm from the same file becomes two entities:
error: conflicting declaration 'struct tm'
note: previous declaration as 'struct tm' (of module gnome.glib)
Which route to pick is decided by macros. A module cannot carry them, and glib's are half its API:
| declarations | #define |
|
|---|---|---|
| glib | 1,312 | 1,337 |
| gobject | 243 | 339 |
| gio | 1,753 | 1,679 |
G_DEFINE_TYPE, G_OBJECT, g_signal_connect, every G_TYPE_* — macros. So
code that defines a GObject subclass takes the header route. Code that uses
the function API — most of gio — takes the module route and includes
nothing. Each member ships a test for each route, so both stay working.
Why a module at all, when the headers work: in this index the namespace is a
contract. compat.xxx means headers; an owner namespace like gnome.xxx
means the package exposes import, the way freedesktop.cairo,
wlroots.wlroots and freedesktop.wayland do. gnome.* promised it and did
not deliver, which is a wrong promise rather than a missing nicety.
The package version is upstream's, with nothing appended. 2.82.5 means
GLib 2.82.5. When this fork changes and upstream does not, the tag is re-cut in
place and the index descriptor gets a new sha256; there is no fork-revision
component to read.
That has one consequence worth knowing: the package store keys on
(name, version), so a machine that already extracted 2.82.5 keeps what it
extracted. If a fix landed and you do not see it, clear that entry.
2.82.5 — the one cut before gnome.gio existed — shipped four
wrong macro names in gnome.gobject: G_UNICODE_TYPE_TYPE where upstream has
G_TYPE_UNICODE_TYPE, and three like it. Every function name was right, so it
compiled, linked, and passed a test that checked the function and the nick.
Fixed in the current 2.82.5.
Generators, not line count — the same criterion that made cairo (104k lines) a plain descriptor and libdisplay-info (2k) a fork. GLib has seven, and this fork adds an eighth of its own:
| upstream | here |
|---|---|
tools/gen-visibility-macros.py versions-macros |
gen_version_macros() |
tools/gen-visibility-macros.py visibility-macros ×4 |
gen_visibility() |
configure_file → glibconfig.h |
gen_glibconfig() |
configure_file → gmoduleconf.h |
gen_gmoduleconf() |
configure_file → gnetworking.h |
gen_gnetworking() |
configure_file → config.h |
gen_config() |
gobject/glib-mkenums (816 lines of Python) |
write_enumtypes() |
gio/gdbus-2.0/codegen (8,351 lines of Python) |
checked in — see below |
| (none — this fork's own) | gen_module() → the four .cppm wrappers |
There is no sh and no python in the build. build.mcpp is a compiled
C++ program, so every generator above except the last is a function in it.
gdbus-codegen turns D-Bus interface XML into GObject skeletons, proxies and
marshalling. Reproducing it would be a rewrite, not a reimplementation — and
that was once given as the reason gio was absent. It was the wrong reason,
because it conflated two different things:
reproducing the generator ≠ obtaining its output
The build does not need the generator. It needs 15,392 lines of C that are a
pure function of five XML files and the codegen version — no target, no host,
no locale enters it. So mcpp/tools/gengdbus.sh produces them once, they live
in mcpp/generated/, and CI regenerates and diffs. That last part is not
optional: committed output has exactly one failure mode a build never sees — it
can stop matching its input, and it still compiles.
Same arrangement as mcpplibs/wayland-protocols and its 195 generated files.
The script runs out of tree, which is not tidiness: codegen/config.py is
itself a configure_file, and CPython writes __pycache__/ on import. Both
would land in upstream/ and fail the "release tarball, unmodified" job.
| enums | annotations | |
|---|---|---|
gobject |
4, from glib/gunicode.h |
none |
gio |
82, across the 152 headers gio.h reaches (3 of which have any) |
/*< flags >*/ ×7, /*< nick=… >*/ ×17, /*< prefix=… >*/ ×1 |
What changes at gio's scale is not the loop but the annotations — and one trap in each direction:
- gio writes
/*< private >*/,/*< public >*/and/*< protected >*/144 times. Those are GTK-DOC annotations for struct members and mean nothing to mkenums. A scanner that read every/*< … >*/would silently drop enumerators. GConverterFlagshas no/*< flags >*/despite the name, so upstream registers it withg_enum_register_static. A scanner that guessed from the type name would disagree with upstream's ABI.
⭐ And the prefix rule, which shipped wrong once. mkenums has two
prefixes: enum_prefix, taken from the ENUMERATORS, drives the nicks; and
@ENUMPREFIX@, taken from the TYPE NAME, drives the macro. Conflating them
gives G_UNICODE_TYPE_TYPE instead of G_TYPE_UNICODE_TYPE — which compiles,
links, and passes any test that checks the function or the nick. CI now checks
the macro, the nick, and enum-vs-flags separately, because they fail
independently.
Every one of these produced a wrapper that compiled, and every one was found by a consumer or by the other toolchain — never by the build:
| what happened | |
|---|---|
= '{' |
G_VARIANT_CLASS_DICT_ENTRY = '{' is a brace in a char literal. The typedef reader counted it, never closed, and swallowed the rest of gvariant.h — every g_variant_* function gone, while 1,853 other names made the module look complete. |
G_DECLARE_INTERFACE |
expands to the typedef and _get_type, so a text scanner sees neither. GListModel, GListStore and g_list_model_get_type were missing — the exact symbols pango is waiting on. |
<glibconfig.h> |
spelled with no glib/ prefix, so deriving the header set from the umbrella's <glib/…> lines missed it — and it is where gsize and gssize are typedef'd. |
void (g_free) (…) |
glib parenthesises names to defend them from macros, putting the declarator at paren depth 1. A depth-0 rule dropped g_free, g_string_free and the GVariant constructors. |
And one that only one toolchain could show:
typedef enum { G_MODULE_BIND_LAZY, … } GModuleFlags;— exporting the typedef makes the enumerators visible on GCC. Clang rejects the same file withuse of undeclared identifier 'G_MODULE_BIND_LAZY'. Enumerators are now exported by name. This is the single best argument in this fork for CI running both toolchains.
The floors in CI (glib ≥ 1900, gio ≥ 2400) exist for exactly this: a
collapse is silent, so something has to count.
mcpp/common/generators.h holds them; each member has a thirty-line
build.mcpp that calls the ones it needs and writes into ITS OWN include/.
../include/gobject/glib-enumtypes.c — a file glib's build program produced.
With gobject AND gmodule both named by one consumer, mcpp resolved the two
../glib path dependencies to ONE package, so only one member's include/ was
ever written, and the source entry pointing into the other simply matched
nothing. No "file not found": the object was absent from the ninja file
entirely, and it surfaced as
undefined reference to `g_unicode_script_get_type'
in a consumer that named both. Naming a file another package's build program produces is the mistake. The generators are deterministic, so four copies cannot disagree.
gobject, gmodule and gio depend on glib by workspace path — which also
means a consumer must NOT name glib itself:
error: dependency 'gnome.glib' is requested as both a version dep and a path dep
Name what you use; glib arrives transitively.
glib's sources say #include "config.h", and config.h is the most contested
file name in C. A build program's own mcpp::include_dir() is appended after
every dependency's, so a generated config.h placed there loses to any
dependency that ships one — measured in the wlroots fork at 59th of 59.
Manifest entries come first.
include/.gitkeep is committed because mcpp builds the compiler command line
before running the build program and silently drops an include_dirs entry
that does not exist yet.
| what happened | |
|---|---|
#define HAVE_ISSETUGID 0 |
autotools probes are #ifdef-tested, so 0 means yes. glib called a BSD interface glibc does not have. Nine such macros are now absent rather than 0. |
GLIB_USING_SYSTEM_PRINTF |
the wrong one of two near-identical names. USE_SYSTEM_PRINTF (config.h) selects; the other (glibconfig.h) only reports. Setting only the second left glib calling its bundled gnulib printf, and none of glib/gnulib is compiled — a page of undefined reference to _g_gnulib_snprintf. |
STRERROR_R_CHAR_P |
glibc has two strerror_r. With _GNU_SOURCE it returns char*; the XSI one returns int. Choosing wrong is a compile error, which is the good case. |
And one in the other direction: gmoduleconf.h.in tests its values with
#if (@X@), so there 0 is correct and omission is a syntax error. The test
style decides, every time.
gio is assembled from four kinds of input, and three of them degrade silently
if left out — so each is checked by name in mcpp/gio/tests/gio.cpp:
| absent, you get | |
|---|---|
gio/xdgmime/ |
g_content_type_guess answers application/octet-stream for everything |
gio/inotify/ |
the file monitor quietly falls back to polling |
subprojects/gvdb/ |
GResource and GSettings' schema reader are never reachable |
mcpp/generated/ |
it still compiles; the portal calls fail only inside a Flatpak sandbox |
Not built: the ten tools (gio-tool, glib-compile-schemas,
gdbus-tool, …). Each provides main, which collides with the consumer's —
the rule every package in this index follows. gconstructor_as_data.h goes with
them, since it exists only for glib-compile-resources.
No external dependency but zlib. Upstream's meson names exactly one other,
libelf, and it is used by gresource-tool.c — an executable(), not the
library. There is therefore no libelf feature, because there is nothing for it
to switch. libmount, selinux and sysprof are auto upstream and absent
here; each is #ifdef-tested, so each is absent rather than 0, and gio
degrades the way upstream intends.
upstream/ glib 2.82.5, byte for byte (CI diffs it)
mcpp/common/
generators.h the generators
prelude.h the four every member needs, plus gio's three
mcpp/generated/ gdbus-codegen output, 15,392 lines (CI regenerates+diffs)
mcpp/tools/
gengdbus.sh the maintainer script that produces the above
mcpp/glib/
mcpp.toml `include` FIRST in include_dirs, deliberately
build.mcpp thirty lines: the shared four
include/ GENERATED, not checked in (only .gitkeep)
tests/glib.cpp
mcpp/gobject/ + gobject-visibility.h and glib-enumtypes.{h,c}
mcpp/gmodule/ + gmodule-visibility.h and gmoduleconf.h (flat)
mcpp/gio/ + gio-visibility.h, gnetworking.h, gioenumtypes.{h,c}