Files
RedBear-OS/local/docs/PACKAGE-BUILD-QUIRKS.md
T

42 KiB

Package Build Quirks — Known Cross-Compilation Issues and Fixes

Created: 2026-06-19 Scope: All recipes in recipes/ and local/recipes/ that have known build quirks Purpose: Central reference for cross-compilation issues encountered when building Red Bear OS packages, their root causes, and fixes.


Overview

Building packages for the x86_64-unknown-redox target from a Linux x86_64 host involves a cross-compilation environment managed by the cookbook tool. Several packages require specific adjustments to their recipe or source to build correctly in this environment. This document captures all known quirks and their fixes.


Cookbook Environment: DYNAMIC_INIT

Problem

The cookbook's DYNAMIC_INIT function (defined in src/cook/script.rs) sets up the cross-compilation environment. It overwrites several environment variables entirely, including:

  • CFLAGS — set to -I<sysroot>/include
  • CXXFLAGS — set to -I<sysroot>/include
  • LDFLAGS — set to -L<sysroot>/lib --static -L<prefix>/lib
  • CPPFLAGS — set to -I<sysroot>/include
  • PKG_CONFIG_LIBDIR, PKG_CONFIG_SYSROOT_DIR
  • RUSTFLAGS
  • CARGO_TARGET_DIR
  • PATH

Impact

Any export FOO="..." that runs BEFORE DYNAMIC_INIT will be overwritten. Any export FOO="..." that runs AFTER DYNAMIC_INIT will persist.

Fix Pattern

For variables that DYNAMIC_INIT overwrites, you must set them AFTER DYNAMIC_INIT:

# WRONG: LDFLAGS overwritten by DYNAMIC_INIT
export LDFLAGS="-Wl,--allow-multiple-definition ${LDFLAGS}"
DYNAMIC_INIT
# At this point, LDFLAGS = "-L<sysroot>/lib --static ..." (our flag is gone)

# CORRECT: Set after DYNAMIC_INIT preserves the flag
DYNAMIC_INIT
export LDFLAGS="-Wl,--allow-multiple-definition ${LDFLAGS}"
# At this point, LDFLAGS = "-Wl,--allow-multiple-definition -L<sysroot>/lib ..."

For CFLAGS specifically, the pre-DYNAMIC_INIT approach can work because the reexport_flags function (called by cookbook_configure) re-reads the current $CFLAGS and incorporates it into CFLAGS_x86_64_unknown_redox. But this is fragile — the safest approach is to set flags AFTER DYNAMIC_INIT for ALL variables.

Exceptions

CFLAGS set BEFORE DYNAMIC_INIT works because reexport_flags (called inside cookbook_configure) reads $CFLAGS at configure time and prepends it to CFLAGS_x86_64_unknown_redox. However, LDFLAGS set BEFORE DYNAMIC_INIT is overwritten by DYNAMIC_INIT and NOT re-read by reexport_flags.

Safe rule: Set ALL custom flags AFTER DYNAMIC_INIT.

Reference

  • src/cook/script.rsDYNAMIC_INIT function definition
  • src/cook/script.rsreexport_flags function definition

Package: m4 (GNU M4 macro processor)

Recipe: local/recipes/dev/m4/recipe.toml Source: https://ftp.gnu.org/gnu/m4/m4-1.14.21.tar.xz Template: custom (uses cookbook_configure)

Quirk 1: -fcommon Required (GCC 10+ Compatibility)

Problem: GCC 10+ defaults to -fno-common, which causes multiple definition errors for gnulib's rpl_realloc wrapper. The gnulib replacement function is defined as a tentative symbol across multiple translation units.

Error:

/usr/bin/x86_64-unknown-redox-ld: libm4.a(libm4_a-malloc.o):(.bss+0x0):
  multiple definition of `rpl_realloc';
  libm4_a-realloc.o:(.bss+0x0): first defined here

Fix: Add -fcommon to CFLAGS:

export CFLAGS="-fcommon ${CFLAGS}"

Placement: BEFORE DYNAMIC_INIT (works because reexport_flags re-reads $CFLAGS at configure time).

Better placement (recommended for future): AFTER DYNAMIC_INIT — same result, more robust.

Quirk 2: --allow-multiple-definition Required (Linker)

Problem: Even with -fcommon, the linker encounters multiple definitions of rpl_realloc because gnulib provides it in multiple object files and the static library linking order causes duplicates.

Error:

/usr/bin/x86_64-unknown-redox-ld: libm4.a(libm4_a-malloc.o):(.bss+0x0):
  multiple definition of `rpl_realloc';
  libm4_a-realloc.o:(.bss+0x0): first defined here
/usr/bin/x86_64-unknown-redox-ld: error: ld returned 1 exit status

Fix: Add --allow-multiple-definition to LDFLAGS:

DYNAMIC_INIT
export LDFLAGS="-Wl,--allow-multiple-definition ${LDFLAGS}"

Placement: MUST be AFTER DYNAMIC_INITDYNAMIC_INIT overwrites LDFLAGS entirely. Setting it before DYNAMIC_INIT has no effect.

Quirk 3: Locale Platform Detection

Problem: m4's gnulib getlocalename_l-unsafe.c has a Redox case guarded by HAVE_GETLOCALENAME_L, which relibc doesn't define. The file hits an #error when neither the guard nor the platform detection matches.

Error:

error: #error "Please port gnulib getlocalename_l-unsafe.c to your platform"

Fix: Changed the guard from __redox__ && HAVE_GETLOCALENAME_L to just __redox__ and return "C" (Redox only supports the C/POSIX locale).

File: lib/getlocalename_l-unsafe.c — line ~661:

#elif defined __redox__
  return "C";

Applied via: local/recipes/dev/m4/source/ (local recipe with source bundled)

Quirk 4: Configure Cache Variables

Problem: m4's configure script tests for functions that don't exist or behave differently on Redox, causing false negatives.

Fix: Pass cache variables to skip problematic tests:

COOKBOOK_CONFIGURE_FLAGS+=(
    --disable-nls
    ac_cv_func___freadahead=yes
    ac_cv_have_decl___freadahead=yes
    gl_cv_header_wchar_h_correct_inline=yes
    gl_cv_func_btowc_nul=yes
    gl_cv_func_btowc_consistent=yes
    gl_cv_onwards_func___freadahead=yes
)

Quirk 5: wchar.h Circular Include (gnulib + relibc)

Problem: m4's gnulib generates a wchar.h wrapper that does #include_next <wchar.h>. relibc's wchar.h included <stdio.h> at line 4, before defining wint_t (line 20) and mbstate_t (line 41). The circular chain:

wchar.h -> stdio.h -> stdint.h -> gnulib inttypes.h -> sysroot inttypes.h -> #include wchar.h -> BACK TO GNULIB wchar.h (guard not set!)

caused gnulib's wchar.h to process its replacements before wint_t/mbstate_t were defined, producing unknown type name 'wint_t' and unknown type name 'mbstate_t'.

Error:

error: unknown type name 'wint_t'
error: unknown type name 'mbstate_t'

Root cause: relibc's wchar.h included <stdio.h> before defining wint_t/mbstate_t, AND relibc's inttypes.h included <wchar.h> instead of <stdint.h> + <stddef.h> per POSIX spec.

Fix: Two parts, both in the relibc fork (local/sources/relibc/):

  1. src/header/wchar/cbindgen.toml: Moved stdio.h/time.h from sys_includes into after_includes, placed AFTER the wchar_t/wint_t/mbstate_t typedefs. Also defined mbstate_t in after_includes with a _RELIBC_MBSTATE_T guard and excluded it from cbindgen export via [export] exclude = ["mbstate_t"].
  2. src/header/inttypes/cbindgen.toml: Changed sys_includes from ["wchar.h"] to ["stdint.h", "stddef.h"] per POSIX spec (inttypes.h only needs wchar_t from stddef.h, not the full wchar.h).

Reference: See the relibc section below (Quirk 3) for the same fix documented from the relibc side. Commits d28963d (wchar.h fix) and a2e4cd2 (inttypes.h fix) in local/sources/relibc/.

Quirk 6: __freadahead Prefix Staleness

Problem: The recipe sets ac_cv_func___freadahead=yes to tell gnulib that relibc provides __freadahead. The function IS implemented in relibc's src/header/stdio/ext.rs (commit 047e7c0). But the PREFIX libc.a was built before that commit, so the symbol was missing from the compiled library.

Error:

undefined reference to `__freadahead'

Fix: Rebuild relibc in the prefix after adding new functions:

touch relibc && make prefix

Prevention: The build script now warns when prefix artifacts are older than fork commits.

Complete Recipe

[source]
tar = "https://ftp.gnu.org/gnu/m4/m4-1.14.21.tar.xz"
patches = ["redox.patch"]

[build]
template = "custom"
script = """
export CFLAGS="-fcommon ${CFLAGS}"
DYNAMIC_INIT
export LDFLAGS="-Wl,--allow-multiple-definition ${LDFLAGS}"
COOKBOOK_CONFIGURE_FLAGS+=(
    --disable-nls
    ac_cv_func___freadahead=yes
    ac_cv_have_decl___freadahead=yes
    gl_cv_header_wchar_h_correct_inline=yes
    gl_cv_func_btowc_nul=yes
    gl_cv_func_btowc_consistent=yes
    gl_cv_onwards_func___freadahead=yes
)
cookbook_configure
"""

[package]
description = "GNU M4 macro processor"

Note: Quirks 5 and 6 do not require changes to recipe.toml. Quirk 5 is fixed in the relibc fork (header generation), and Quirk 6 is resolved by rebuilding the relibc prefix artifact (touch relibc && make prefix).


Package: ninja-build (Ninja build system)

Recipe: local/recipes/dev/ninja-build/recipe.toml Source: https://github.com/ninja-build/ninja (v1.13.1) Template: cmake

Quirk: BUILD_TESTING=OFF Required (Cross-Compilation)

Problem: ninja-build's CMakeLists.txt includes CTest, which enables BUILD_TESTING=ON by default. Tests require Google Test (gtest), which is found on the host system at /usr/include/gtest/. When the C++ compiler includes gtest headers, it transitively includes the host /usr/include/stdlib.h, which conflicts with the Redox sysroot stdlib.h.

Error:

/usr/include/stdlib.h:67:5: error: conflicting declaration 'typedef struct div_t div_t'
/usr/include/stdlib.h:75:5: error: conflicting declaration 'typedef struct ldiv_t ldiv_t'
/usr/include/stdlib.h:85:5: error: conflicting declaration 'typedef struct lldiv_t lldiv_t'
/usr/include/stdlib.h:761:18: error: conflicting declaration of 'int at_quick_exit(void (*)())' with 'C++' linkage

Additionally, the Redox sysroot stdlib.h uses __noreturn and __deprecated attributes that C++ doesn't recognize, causing further errors when test files are compiled.

Root cause: This is NOT a Redox capability gap — it is a fundamental cross-compilation constraint. Test suites require a test framework (gtest) that exists on the host, and tests are designed to run on the target platform, not during cross-compilation. Building tests during cross-compile is incorrect.

Fix: Disable test building via CMake flag:

[build]
template = "cmake"
cmakeflags = ["-DBUILD_TESTING=OFF"]

This is standard practice for cross-compilation — the ninja binary itself is the deliverable, not its test suite.

Complete Recipe

[source]
git = "https://github.com/ninja-build/ninja"
rev = "v1.13.1"

[build]
template = "cmake"
cmakeflags = ["-DBUILD_TESTING=OFF"]

[package]
description = "Ninja build system"

Package: kernel (Redox kernel — local fork)

Recipe: recipes/core/kernel/recipe.toml (uses path = "../../../local/sources/kernel") Source: local/sources/kernel/ (local fork)

Quirk: -Zjson-target-spec Cargo Flag

Problem: The upstream kernel Makefile passes -Zjson-target-spec as a cargo CLI flag. The redoxer build environment's cargo accepts it in cargo -Z help but rejects it when invoked through make inside redoxer env bash -ex.

Fix: Use the env-var form instead of the CLI flag in the kernel Makefile:

export CARGO_UNSTABLE_JSON_TARGET_SPEC=true
# Remove -Zjson-target-spec from the cargo rustc command line

General Cross-Compilation Patterns

Pattern: Disable Tests During Cross-Compilation

When a package's build system includes test targets (cmake BUILD_TESTING, automake TESTS, meson tests), tests must be disabled during cross-compilation.

Why: Tests require:

  1. A test framework (gtest, catch2, etc.) that exists on the HOST, not the TARGET
  2. The test framework headers transitively include HOST system headers
  3. HOST system headers conflict with TARGET sysroot headers (type redefinition)
  4. Tests are designed to RUN on the target, not during cross-compile

How (cmake):

cmakeflags = ["-DBUILD_TESTING=OFF"]

How (meson):

mesonflags = ["-Dtests=false"]

How (configure/automake):

COOKBOOK_CONFIGURE_FLAGS+=(--disable-tests)
# or if no --disable-tests option:
COOKBOOK_CONFIGURE_FLAGS+=(ac_cv_prog_have_gtest=no)

Pattern: Gnulib Compatibility (-fcommon and --allow-multiple-definition)

Packages using gnulib (m4, bison, flex, autoconf, etc.) may need:

  1. -fcommon in CFLAGS — gnulib provides replacement functions (rpl_malloc, rpl_realloc) that are defined as tentative symbols in multiple translation units. GCC 10+ defaults to -fno-common, causing multiple definition errors.

  2. -Wl,--allow-multiple-definition in LDFLAGS — Even with -fcommon, the linker may encounter duplicate definitions from static library archives.

Note: These flags should be added per-recipe only when needed, not globally.

Pattern: Configure Cache Variables for Autoconf Packages

Autoconf-based packages (m4, bison, flex, etc.) run configure tests that may fail on Redox because:

  • The function exists but behaves differently
  • The function doesn't exist (but the test expects it)
  • Cross-compilation can't run test programs

Fix by passing cache variables to skip problematic tests:

COOKBOOK_CONFIGURE_FLAGS+=(
    ac_cv_func___freadahead=yes
    ac_cv_have_decl___freadahead=yes
    gl_cv_header_wchar_h_correct_inline=yes
)

Pattern: NLS Disabled

Most packages should disable NLS (Native Language Support) for Redox:

COOKBOOK_CONFIGURE_FLAGS+=(--disable-nls)

Redox doesn't have a full gettext/libintl stack. The --disable-nls flag is standard for cross-compilation targets that don't need internationalization.

Pattern: C++ Cross-Compilation Header Isolation

When compiling C++ files for Redox, the compiler must use the Redox sysroot headers, not the host headers. The cookbook sets up --sysroot via the CMake toolchain file and CFLAGS_x86_64_unknown_redox/CXXFLAGS_x86_64_unknown_redox.

If host headers leak in (usually through #include <gtest> or similar), the fix is to remove the dependency on host headers, not to try to isolate the include path further. This typically means disabling tests or the feature that requires the host-only dependency.


Package: libiconv (GNU libiconv 1.17)

Quirk: Libtool Version Mismatch (2.6.0.23-b08cb)

Problem

libiconv 1.17 ships a pre-generated configure script that embeds the libtool release identifier at configure-generation time. On Debian hosts where the system libtool has been updated to 2.6.0.23-b08cb, the bundled configure declares macro_version='2.6.0', but the running libtool binary advertises VERSION=2.6.0.23-b08cb and package_revision=2.6.0.23. At compile time, libtool aborts with:

libtool: Version mismatch error.  This is libtool 2.6.0.23-b08cb, revision 2.6.0.23,
libtool: but the definition of this LT_INIT comes from revision 2.6.0.
libtool: You should recreate aclocal.m4 with macros from revision 2.6.0.23
libtool: of libtool 2.6.0.23-b08cb and run autoconf again.

This affects the nested libcharset subdir as well — both configure scripts need patching.

Root Cause

ltmain.sh (the libtool script template that ships with the host's libtool package) is the source of truth for the running libtool's VERSION and package_revision. The bundled configure script was generated against an older libtool and embeds the older macro_version/ macro_revision strings. When libtool runs at compile time it compares its own VERSION/package_revision against the values configure generated, and aborts when they differ.

Fix

Source the shared helper from the build script and rewrite both configure scripts' macro_version and macro_revision to match the host libtool:

. "${COOKBOOK_ROOT}/local/scripts/lib/libtool-version-sync.sh"
redbear_sync_libtool_version \
    "${COOKBOOK_SOURCE}/configure" \
    "${COOKBOOK_SOURCE}/libcharset/configure"

The helper detects the host libtool version (via /usr/bin/libtool --versionmust be the absolute path, not command -v libtool, because the cookbook prepends the Redox cross sysroot's bin/ to PATH and command -v libtool would return the Redox-target libtool 2.5.4-redox-9510 instead of the host toolchain's libtool) and rewrites the known bundled version strings (2.4.7, 2.5.4-redox-9510, 2.6.0) to the host values. Other recipes that bundle pre-generated configure scripts (older gettext, autotools packages from before the host libtool was updated) can use the same helper.

Reference

  • local/scripts/lib/libtool-version-sync.sh — shared helper
  • recipes/libs/libiconv/recipe.toml — usage in the [build] script
  • redoxer host: libtool (GNU libtool) 2.6.0.23-b08cb
  • Upstream libtool version detection:
    • libtool --version (preferred)
    • VERSION= line in /usr/share/libtool/build-aux/ltmain.sh (fallback)

Package: gnu-binutils (GNU Binutils 2.43.1)

Recipe: recipes/tools/gnu-binutils/recipe.toml Source: https://ftp.gnu.org/gnu/binutils/binutils-2.43.1.tar.xz Template: custom (uses cookbook_configure)

Quirk: Autoconf Version Lock — Skip Autoreconf

Problem: Binutils 2.43.1 ships config/override.m4 which enforces exactly Autoconf 2.69 via _GCC_AUTOCONF_VERSION_CHECK. If the host has Autoconf 2.73 (common on modern Debian/Ubuntu), autoreconf aborts:

configure.ac:21: error: Please use exactly Autoconf 2.69 instead of 2.73.

The original recipe used COOKBOOK_AUTORECONF=autoreconf2.69 which requires the Debian-specific autoconf2.69 package — not available on all distros.

Root cause: GCC/Binutils lock their autoreconf to a specific Autoconf version. This is intentional upstream behavior, not a bug.

Fix: Skip the autoreconf step entirely. The source tarball ships with pre-generated configure scripts that work correctly without regeneration. The 01_build_fix.patch modifies configure.ac, but the pre-generated configure scripts remain functional for cross-compilation.

Recipe change: Removed the autotools_recursive_regenerate call and the COOKBOOK_AUTORECONF override from the fetch script. Only the config.sub/config.guess/install-sh copy step remains.


Package: gettext (GNU gettext 0.22.5)

Recipe: recipes/tools/gettext/recipe.toml Source: https://ftp.gnu.org/gnu/gettext/gettext-0.22.5.tar.gz Template: custom (uses cookbook_configure)

Quirk: libintl.a Not Installed by Default

Problem: gettext's make install skips installing libintl.a and libintl.h. The Makefile has install-exec-libintl commented out with the note: "We must not install the libintl.h/libintl.la files."

Downstream packages (glib, etc.) that depend on intl via meson's dependency lookup fail with:

Run-time dependency intl found: NO  (tried builtin and system)

Root cause: gettext's configure detects the C library might already provide gettext functions and disables separate libintl installation. On Redox (relibc), gettext functions are NOT provided by the C library, so libintl must be installed separately.

Fix: Two changes in the recipe build script:

  1. Pass --with-included-gettext to configure to force building libintl from the included source.
  2. Manually install libintl.a and libintl.h after cookbook_configure:
cookbook_configure
cp -f gettext-runtime/intl/.libs/libgnuintl.a "${COOKBOOK_STAGE}/usr/lib/libintl.a"
cp -f gettext-runtime/intl/libgnuintl.h "${COOKBOOK_STAGE}/usr/include/libintl.h"

The library is built as libgnuintl.a (GNU internal name) and must be installed as libintl.a (POSIX name that meson/pkg-config expects).


Package: glib (GLib 2.87.0)

Recipe: recipes/libs/glib/recipe.toml Source: https://download.gnome.org/sources/glib/2.87/glib-2.87.0.tar.xz Template: custom (uses cookbook_meson)

Quirk: Source Tarball Contains .orig Files (False Positive in Patch Checker)

Problem: glib's source tarball legitimately includes .orig files as part of its test suite:

gio/tests/org.gtk.test.gschema.xml.orig
gio/tests/org.gtk.test.gschema.override.orig

The cookbook's atomic patch checker (src/cook/fetch.rs) detects .orig files after patch application and treats them as evidence of failed hunks, rolling back the patch and aborting the build.

Root cause: The patch checker did not distinguish between pre-existing .orig files (from the source tarball) and newly-created .orig files (from patch command failures).

Fix: Updated the atomic patch checker in src/cook/fetch.rs to snapshot pre-existing .orig files before patch application, and only flag .orig files that were NOT in the pre-existing set. The --no-backup-if-mismatch flag (already used) prevents patch from creating new .orig files, so any .orig file found after patching that was in the pre-existing set is safe to ignore.


Package: relibc (Redox C Library — local fork)

Recipe: recipes/core/relibc/recipe.toml (uses path = "../../../local/sources/relibc") Source: local/sources/relibc/ (local fork)

Quirk 1: cbindgen Generates ... Instead of va_list

Problem: cbindgen translates Rust's VaList<'_> type as ... (variadic) in generated C headers. This affects all v*printf functions:

// Generated (WRONG):
int vsnprintf(char *s, size_t n, const char *format, ...);

// Expected (CORRECT):
int vsnprintf(char *s, size_t n, const char *format, va_list ap);

This breaks C++ standard library code that takes the address of vsnprintf (e.g., std::to_string(float)__to_xstring(&std::vsnprintf, ...)), causing compilation failures in Qt6 and other C++ packages.

Root cause: cbindgen does not natively understand the va_list crate's VaList type and defaults to generating ....

Fix: Post-generation sed in the relibc recipe build script:

sed -i '/^int vfprintf(/s/\\.\\.\\./va_list ap/' "${COOKBOOK_STAGE}/usr/include/stdio.h"
sed -i '/^int vsnprintf(/s/\\.\\.\\./va_list ap/' "${COOKBOOK_STAGE}/usr/include/stdio.h"
sed -i '/^int vsprintf(/s/\\.\\.\\./va_list ap/' "${COOKBOOK_STAGE}/usr/include/stdio.h"

Note: The \\. sequences are required because TOML triple-quoted strings interpret \\ as a literal \, producing the sed pattern \.\.\.

Quirk 2: VaList API Version Mismatch Between Toolchains

Problem: relibc HEAD uses VaList<'a> (1 lifetime) and VaList::next_arg() from Rust 1.98-dev. The PREFIX toolchain (nightly-2025-11-15) has the older VaList<'a, 'f: 'a> (2 lifetimes) with no next_arg() method.

When building via make live, mk/prefix.mk:24 exports REDOXER_TOOLCHAIN=PREFIX, forcing the older Rust. This causes compilation errors in relibc.

Fix: Override RUSTUP_TOOLCHAIN in the relibc recipe build script to use the redoxer default toolchain (~/.redoxer/${TARGET}/toolchain, Rust 1.98-dev) when REDOXER_TOOLCHAIN is set and not pointing to a .partial directory (PREFIX bootstrap):

REDOXER_TC="${REDOXER_TOOLCHAIN:-}"
if [ -n "${REDOXER_TC}" ] && [[ "${REDOXER_TC}" != *".partial"* ]]; then
    REDOXER_DEFAULT="${HOME}/.redoxer/${TARGET}/toolchain"
    if [ -d "${REDOXER_DEFAULT}/lib/rustlib/src/rust/library/core" ]; then
        export RUSTUP_TOOLCHAIN="${REDOXER_DEFAULT}"
        export PATH="${REDOXER_DEFAULT}/bin:${PATH}"
    fi
fi

Quirk 3: wchar.h / inttypes.h Circular Include Chain

Problem: relibc's generated wchar.h included <stdio.h> (via cbindgen sys_includes) before defining wint_t and mbstate_t. And inttypes.h included <wchar.h> for wchar_t instead of using stddef.h per POSIX spec. This created a circular include chain that broke gnulib-based packages (m4, bison, flex, etc.).

Error:

error: unknown type name 'wint_t'
error: unknown type name 'mbstate_t'

Root cause: Two intertwined issues in relibc's cbindgen header generation:

  1. wchar/cbindgen.toml listed stdio.h and time.h in sys_includes, which cbindgen emits at the top of the generated header, before the wchar_t/wint_t/mbstate_t typedefs. When a gnulib wrapper did #include_next <wchar.h>, the preprocessor pulled in stdio.h -> stdint.h -> inttypes.h -> wchar.h again before any of those types were defined.
  2. inttypes/cbindgen.toml listed wchar.h in sys_includes. Per POSIX, inttypes.h only needs wchar_t (from stddef.h), not the full wchar.h. Including wchar.h closed the circular chain.

Fix: Both changes are in the relibc fork (local/sources/relibc/):

  1. src/header/wchar/cbindgen.toml: sys_includes = ["features.h"] only. The type definitions (wchar_t, wint_t, mbstate_t) and the stdio.h/ time.h includes were moved to after_includes, so the typedefs land before any transitive include. mbstate_t is defined in after_includes under a _RELIBC_MBSTATE_T guard, and excluded from cbindgen export via [export] exclude = ["mbstate_t"].
  2. src/header/inttypes/cbindgen.toml: sys_includes = ["stdint.h", "stddef.h"] (POSIX spec compliant, breaks the circular chain by removing the inttypes.h -> wchar.h edge).

Commits: d28963d (wchar.h fix), a2e4cd2 (inttypes.h fix) in local/sources/relibc/.

Reference: The m4 section (Quirk 5) documents the same fix from the consuming package's perspective.


Package: qtbase (Qt 6 Base — local recipe)

Recipe: local/recipes/qt/qtbase/recipe.toml Source: local/recipes/qt/qtbase/source/ (local overlay with tar source) Template: custom

Quirk 1: Source Tarball Not Extracted for Local Overlay Recipes

Problem: The cookbook treats local/recipes/qt/qtbase/source/ as a local overlay (immutable). The is_local_overlay() check prevents wiping and re-extracting the source from source.tar. As a result, only the Red Bear-specific files (tests/, util/) exist in source/, but the full Qt6 source code is missing.

Error:

FileNotFoundError: No such file or directory: '.../source/src/tools/CMakeLists.txt'

Fix: Manually extract the source tarball into the source/ directory:

cd local/recipes/qt/qtbase
tar xf source.tar --strip-components=1 -C source/

This must be done once after updating the source tarball. Patches must also be applied manually since the cookbook won't apply them to local overlay sources:

cd source
patch -p1 --batch --no-backup-if-mismatch < ../redox.patch

Affected recipes: All Qt6 local overlay recipes with tar sources: qtbase, qt5compat, qtdeclarative, qtsvg, qtshadertools, qtwayland.

Quirk 2: forkfd.h Missing <sys/resource.h> Include

Problem: src/3rdparty/forkfd/forkfd.h declares functions using struct rusage but only includes <sys/wait.h>. On Linux glibc, <sys/wait.h> transitively includes <sys/resource.h> (which defines struct rusage). On Redox/relibc, this transitive include doesn't exist.

Error:

forkfd.h:57: warning: 'struct rusage' declared inside parameter list
forkfd.c:918: error: conflicting types for 'forkfd_wait4'

Fix: Add #include <sys/resource.h> to forkfd.h:

#include <fcntl.h>
#include <stdint.h>
#include <sys/wait.h>
#include <sys/resource.h>  // Added for struct rusage

Quirk 3: netinet/in6_pktinfo_compat.h Not Available

Problem: src/network/socket/qnativesocketengine_unix.cpp includes <netinet/in6_pktinfo_compat.h>, which relibc does not provide.

Fix: The P0-fix-broken-include.patch removes this include line. Ensure the patch is applied to the source (see Quirk 1 about manual patch application).


Package: redox-driver-sys (Driver System Library)

Recipe: local/recipes/drivers/redox-driver-sys/ Source: local/recipes/drivers/redox-driver-sys/source/ (local recipe)

Quirk: Inline Assembly Register Constraints for I/O Port Access

Problem: The io.rs module uses inline assembly for x86 IN/OUT instructions with generic register constraints (in(reg), out(reg_byte)). The x86 IN/OUT instructions require specific registers:

  • Port: must be DX
  • Data: must be AL (byte), AX (word), or EAX (dword)

Generic constraints let the compiler choose any register, producing invalid instructions like outb ax, esi instead of outb dx, al.

Error:

error: invalid operand for instruction
  --> <inline asm>:2:2
   |
 2 |     outb ax, esi
   |     ^

Fix: Use explicit register operands in src/io.rs:

pub fn inb(port: u16) -> u8 {
    let val: u8;
    unsafe { core::arch::asm!("in al, dx", in("dx") port, out("al") val,
        options(nomem, nostack, preserves_flags)) };
    val
}

pub fn outb(port: u16, val: u8) {
    unsafe { core::arch::asm!("out dx, al", in("dx") port, in("al") val,
        options(nomem, nostack, preserves_flags)) };
}

Apply the same pattern for inw/outw (ax/dx) and inl/outl (eax/dx).


Package: mesa (Mesa 3D Graphics Library)

Recipe: recipes/libs/mesa/recipe.toml Source: https://gitlab.redox-os.org/redox-os/mesa.git (branch redox-24.0) Template: custom (meson-based)

Status (2026-06-20): ALL RED BEAR PATCHES WIRED

Mesa builds successfully for the x86_64-unknown-redox target. The recipe's patches=[...] list contains all six Red Bear Mesa patches, in order:

# Patch Purpose
01 local/patches/mesa/01-virgl-redox-disk-cache.patch Nullifies virgl's disk shader cache on Redox (no XDG_CACHE_HOME semantics on Redox)
02 local/patches/mesa/02-gbm-dumb-prime-export.patch Adds gbm_dri_bo_get_fd fallback path via DRM dumb buffers (PRIME export on Redox)
03 local/patches/mesa/03-platform-redox-gpu-probe.patch EGL platform probe: tells platform_redox.c to enumerate /scheme/drm/cardN and try virgl-capable GPUs
04 local/patches/mesa/04-sys-ioccom-stub-header.patch New file include/sys/ioccom.h (63 lines) implementing BSD-style IOCPARM_LEN / _IO / _IOW / _IOR macros for DRM UAPI ioctl encoding
05 local/patches/mesa/05-vk-sync-wchar-include.patch Adds #include <wchar.h> to vk_sync.h for wchar_t (relibc does not transitively pull in <wchar.h> via <vulkan/vulkan_core.h> the way glibc does)
06 local/patches/mesa/06-redox-surface-image-fields.patch Adds __DRIimage *dri_image_back; and __DRIimage *dri_image_front; to the HAVE_REDOX_PLATFORM section of egl_dri2.h (see quirk below)

Build artifacts (verified 2026-06-20 12:30):

  • recipes/libs/mesa/target/x86_64-unknown-redox/stage/usr/lib/dri/virtio_gpu_dri.so (17,474,080 bytes)
  • stage.pkgar published to local/cache/pkgar/mesa/

Quirk: dri_image_back / dri_image_front fields missing from Redox platform section (RESOLVED)

Problem: src/egl/drivers/dri2/platform_redox.c references dri2_surf->dri_image_back and dri2_surf->dri_image_front, but these fields are only defined inside #ifdef HAVE_ANDROID_PLATFORM in egl_dri2.h. The Redox platform section (#ifdef HAVE_REDOX_PLATFORM) only had orb_window and orb_surface.

Error (prior to fix):

platform_redox.c:65:18: error: 'struct dri2_egl_surface' has no member named 'dri_image_back'

Root cause: When 03-platform-redox-gpu-probe.patch was added to teach platform_redox.c to allocate GBM-backed images for the EGL surface (so the virgl probe can attach to a real GPU), it referenced dri_image_back and dri_image_front. These fields existed only for the Android platform because the original EGL/Redox port only used orb_window / orb_surface (the Orbital display server's native window/buffer types). A complete Redox EGL platform needs both the Orbital legacy fields AND the DRI image fields so a Wayland/DRM-style compositor can attach GBM-backed buffers.

Fix: Patch 06-redox-surface-image-fields.patch adds the missing fields to the HAVE_REDOX_PLATFORM section of src/egl/drivers/dri2/egl_dri2.h:

#ifdef HAVE_REDOX_PLATFORM
   void *orb_window;
   void *orb_surface;
   __DRIimage *dri_image_back;
   __DRIimage *dri_image_front;
#endif

The patch is a 13-line unified diff. With this fix:

  • platform_redox.c (post-03-platform-redox-gpu-probe.patch) compiles cleanly
  • redox_free_images() can now free the GBM-backed images
  • The EGL platform probe can attach a virgl backend to a Redox DRM/KMS surface

Verification: Mesa cook succeeds, virtio_gpu_dri.so links, stage.pkgar produced.

Runtime caveat: The build is verified. The runtime EGL platform selection (EGL_PLATFORM=redox, choosing virtio_gpu_dri.so vs swrast_dri.so) is not yet runtime-validated in a QEMU -device virtio-vga-gl boot. This is the last remaining gap for hardware-accelerated SDDM greeter rendering.

Why -Dstatic_assert(...)=

Mesa's upstream configure (meson) emits -Dstatic_assert=_Static_assert when _Static_assert is unavailable on the target. On glibc/Linux, this collides with the <sys/cdefs.h> static_assert macro. Redox's relibc doesn't define static_assert as a macro, so the explicit = nullifies the macro and lets Mesa use the C11 keyword _Static_assert instead. The recipe applies this via -Dc_args="['-Wno-error=implicit-function-declaration','-Wno-error','-std=gnu11','-Dstatic_assert=_Static_assert']".


Package: sddm (Simple Desktop Display Manager — Wayland-only build)

Recipe: local/recipes/kde/sddm/recipe.toml Source: https://github.com/sddm/sddm.git (rev 63780fcd79f1dbf81a30eef48c28c699ab15aded) Template: custom (cmake-based) **Status (2026-06-20): BUILDS — Wayland-only, all X11 excluded

Build-Side Quirks

SDDM's CMake build assumes X11/XCB/XAUTH are available. Red Bear OS has none of these. The recipe handles this in three coordinated ways:

  1. wayland-patch.sh (~245 lines, sed-based) — applied at build time to:

    • Remove find_package(XCB ...) calls (CMakeLists)
    • Remove XCB include/library refs in src/{daemon,helper,greeter}/CMakeLists.txt
    • Replace cmake/FindXKB.cmake's xcb-xkb lookups with xkbcommon
    • Remove XcbKeyboardBackend.cpp from the greeter
    • Rewrite src/common/XAuth.cpp as a Wayland-only stub
    • Replace Qt5.15.0 CONFIG REQUIRED ... with the components SDDM needs (Core, DBus, Gui, Network, Qml, Quick) + OPTIONAL_COMPONENTS LinguistTools/Test/QuickTest
    • Gate qt_add_translation on Qt6LinguistTools_FOUND
    • Guard the test/CMakeLists.txt block on Qt6Test_FOUND AND Qt6QuickTest_FOUND
  2. remove-x11user-helper.py — strips the sddm-helper-start-x11user add_executable and install(TARGETS sddm-helper-start-x11user ...) from src/helper/CMakeLists.txt, plus the target_link_libraries(sddm-helper-start-x11user ${JOURNALD_LIBRARIES}) line.

  3. stubs/ directory — provides fake headers for headers SDDM #includes but relibc does not yet provide:

    • linux/kd.h, linux/vt.h — VT ioctl definitions (SDDM only uses the ioctl numbers, not the kernel-side semantics)
    • utmpx.h — POSIX utmpx (SDDM only compiles against it; runtime stub)
    • X11/Xauth.h — minimal stub matching the rewritten XAuth.cpp (Wayland-only)

Note on stubs: Per local/AGENTS.md zero-tolerance policy, the X11/Xauth.h stub is a temporary carrier because the corresponding XAuth.cpp is rewritten as a no-op for Wayland-only mode. When relibc gains a real utmpx.h, the utmpx.h stub here should be removed and SDDM should compile against the relibc version. The linux/kd.h and linux/vt.h stubs should be replaced by a proper redbear-input-headers-style header package in a future cleanup pass.

CMake flags that make the build work

The recipe passes these critical flags:

cmake "${COOKBOOK_SOURCE}" \
    -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \
    -DQT_HOST_PATH="${HOST_BUILD}" \
    -DKF6_HOST_TOOLING="${HOST_BUILD}/lib/cmake" \
    -DBUILD_TESTING=OFF \
    -DBUILD_WITH_QT6=ON \
    -DNO_SYSTEMD=ON \
    -DENABLE_JOURNALD=OFF \
    -DENABLE_PAM=ON \
    -DUID_MIN=1000 \
    -DUID_MAX=65000 \
    -DLIBXKB_INCLUDE_DIR="${COOKBOOK_SYSROOT}/usr/include" \
    -DLIBXKB_LIBRARIES="${COOKBOOK_SYSROOT}/usr/lib/libxkbcommon.so" \
    -Wno-dev

Key flags:

  • -DBUILD_WITH_QT6=ON — required; SDDM defaults to Qt5
  • -DNO_SYSTEMD=ON — required; no systemd on Redox
  • -DENABLE_JOURNALD=OFF — required; no journald on Redox
  • -DENABLE_PAM=ON — required; pam-redbear provides libpam.so.0
  • -DUID_MIN=1000 -DUID_MAX=65000 — required at CMake time; SDDM hardcodes these via add_definitions if not provided
  • -DQT_HOST_PATH and -DKF6_HOST_TOOLING — required for moc/qmltyperegistrar/ECMs tools that must run on the host (they cannot cross-compile for the Redox target)

Build verification (2026-06-20)

  • cook sddm - successful (0 errors)
  • Stage output:
    • usr/bin/sddm — main daemon
    • usr/bin/sddm-greeter-qt6 — QML greeter binary
    • usr/libexec/sddm-helper — session spawn helper
    • usr/libexec/sddm-helper-start-wayland — Wayland session starter (forks compositor + greeter)
    • usr/share/sddm/ — themes, scripts, translations
    • etc/pam.d/{sddm,sddm-greeter,sddm-autologin} — PAM service files
  • patchelf --set-rpath "/usr/lib" applied to all binaries so dynamic linking works against the sysroot at runtime

Runtime caveat

The QML greeter (sddm-greeter-qt6) is a Qt6 Wayland client and is therefore gated on the Qt6 Wayland null+8 crash fix (see CONSOLE-TO-KDE-DESKTOP-PLAN.md §3.1). The daemon and helpers will start under init, but the greeter QML window will not appear until the null+8 fix is runtime-validated.


General Pattern: Source Tarball Extraction for Local Overlay Recipes

Problem: When a recipe lives under local/recipes/ (local overlay), the cookbook's is_local_overlay() check prevents wiping and re-extracting the source from source.tar. If the source/ directory only contains Red Bear-specific files (tests, utils, etc.), the main source code is missing.

Affected recipes: Any local overlay recipe that uses tar = "...":

  • qtbase, qt5compat, qtdeclarative, qtsvg, qtshadertools, qtwayland
  • Possibly others added in the future

Fix: Extract the tarball manually before building:

cd <recipe-directory>
tar xf source.tar --strip-components=1 -C source/
# Apply patches manually:
cd source
for patch in ../*.patch; do
    patch -p1 --batch --no-backup-if-mismatch < "$patch" || true
done
# Clean up rejects:
find . -name "*.rej" -delete

Future improvement: The cookbook could be enhanced to support "extract-and-overlay" mode for local recipes with tar sources — extracting the tarball first, then overlaying the local source/ files.


Adding a New Quirk

When you encounter a new build quirk:

  1. Document it here — add a section under the appropriate package, or create a new package section.
  2. Explain the root cause — why does this happen?
  3. Show the fix — what change resolves it?
  4. Note the placement — should the fix go before or after DYNAMIC_INIT?
  5. Is it a cross-compilation pattern? — if so, add it to the General Patterns section so future recipes can benefit.
  6. Update the recipe — apply the fix to recipe.toml.
  7. Update UPSTREAM-SYNC-PROCEDURE.md — if the quirk is related to upstream sync.

Package: base-initfs (initfs image builder)

pcid Must Start Before pcid-spawner

Problem: pcid-spawner calls fs::read_dir("/scheme/pci") as its first action. /scheme/pci is created by pcid via register_sync_scheme(). If pcid is not started, pcid-spawner fails with ENODEV.

Root cause: No init service started pcid in initfs. The upstream model assumed pcid would be started, but no .service file existed for it. This was masked in 0.2.3 because driver-manager (Red Bear's replacement) does its own PCI enumeration via redox_driver_pci and doesn't depend on /scheme/pci.

Fix: Added 35_pcid.service to init.initfs.d/:

[unit]
description = "PCI bus enumeration daemon (creates /scheme/pci)"
requires_weak = ["30_acpid.service"]

[service]
cmd = "pcid"
type = { scheme = "pci" }

The type = { scheme = "pci" } tells init to wait for /scheme/pci to be registered before marking the service as started. This ensures pcid-spawner (which depends on 35_pcid.service via requires_weak) only runs after /scheme/pci exists.

driver-manager Removed During Sync

Problem: The 0.2.3 → 0.2.4 upstream sync silently removed driver-manager from base-initfs dependencies and redbear-device-services.toml packages.

Root cause: Upstream doesn't have driver-manager — it's a Red Bear in-house project. The sync blindly overwrote tracked files with upstream versions.

Fix: Restored:

  • "driver-manager" in base-initfs/recipe.toml dependencies
  • cp driver-manager binary copy to initfs/bin/
  • driver-manager = {} in redbear-device-services.toml [packages]
  • drivers.d/ config directory creation in build script
  • set -eo pipefail in build script

See also: UPSTREAM-SYNC-PROCEDURE.md § "driver-manager: Upstream Not Ready"


Redox Flag Values (Critical for Ported Code)

Redox flag values differ from Linux. Code ported from Linux that assumes Linux conventions will be WRONG on Redox:

Flag Linux Redox
O_RDONLY 0 0x0001_0000
O_WRONLY 1 0x0002_0000
O_RDWR 2 0x0003_0000
O_ACCMODE 3 0x0003_0000
O_CLOEXEC 0x80000 0x0100_0000

Common bug: flags & O_ACCMODE == O_RDONLY evaluates to 0 == 0 on Linux (read-only check passes), but on Redox O_CLOEXEC & O_ACCMODE = 0 ≠ O_RDONLY, so a file opened with O_CLOEXEC without explicit O_RDONLY fails with EROFS.

Fix: Always pass O_RDONLY explicitly when opening for read:

let fd = Fd::open(path, O_RDONLY | O_CLOEXEC, 0)?;

Kernel kcall on ACPI Scheme

Problem: Upstream-synced acpid uses AcpiVerb call API (ReadRxsdt=1, CheckShutdown=2) but the kernel's AcpiScheme didn't implement kcall, returning EOPNOTSUPP.

Fix: Added kcall method to impl KernelScheme for AcpiScheme in src/scheme/acpi.rs. The kernel fork commit is c7af6c43.

Signature:

fn kcall(&self, fds: &[usize], payload: UserSliceRw, flags: CallFlags,
         metadata: &[u64], token: &mut CleanLockToken) -> Result<usize>
  • fds[0] = handle id
  • metadata[0] = verb (u64): 1 = ReadRxsdt, 2 = CheckShutdown
  • payload = UserSliceRw (read/write buffer)

Cross-Reference

Document Relevance
local/docs/UPSTREAM-SYNC-PROCEDURE.md Per-package sync notes include build quirks
AGENTS.md Project-level build commands and conventions
local/AGENTS.md Local fork model, recipe structure
src/cook/script.rs DYNAMIC_INIT, reexport_flags, cookbook_cmake definitions
src/recipe.rs Recipe TOML schema (cmakeflags, configureflags, etc.)