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

1151 lines
42 KiB
Markdown

# 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`:
```bash
# 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.rs``DYNAMIC_INIT` function definition
- `src/cook/script.rs``reexport_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:
```bash
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:
```bash
DYNAMIC_INIT
export LDFLAGS="-Wl,--allow-multiple-definition ${LDFLAGS}"
```
**Placement:** **MUST be AFTER `DYNAMIC_INIT`**`DYNAMIC_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:
```c
#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:
```bash
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:
```bash
touch relibc && make prefix
```
**Prevention:** The build script now warns when prefix artifacts are older than
fork commits.
### Complete Recipe
```toml
[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:
```toml
[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
```toml
[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:
```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):**
```toml
cmakeflags = ["-DBUILD_TESTING=OFF"]
```
**How (meson):**
```toml
mesonflags = ["-Dtests=false"]
```
**How (configure/automake):**
```bash
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:
```bash
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:
```bash
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:
```bash
. "${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
--version` — **must 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`:
```bash
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:
```c
// 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:
```bash
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):
```bash
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:
```bash
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:
```bash
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`:
```c
#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`:
```rust
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`:
```c
#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 `#include`s 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:
```bash
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:
```bash
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/`:
```ini
[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:
```rust
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:**
```rust
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.) |