Building libraygui

The page with no equivalent in the sibling repo, and the one a reader most needs.

b12n-raylib-jlt loads a system-installed libraylib: brew install raylib and you're done. raygui has no such option. raygui is header-only: upstream ships a single raygui.h you #include with RAYGUI_IMPLEMENTATION defined once, and there is no libraygui.so, no Homebrew formula, and nothing on any distro's package index to install. jolt.ffi needs a shared library to load, so this repo builds its own.

What vendor/ holds

  • vendor/raygui.h: a pinned, unmodified copy of upstream's header at revision 5.0-9-gfbf5d95.
  • vendor/raygui_impl.c: the one compilation unit. Two lines: #define RAYGUI_IMPLEMENTATION then #include "raygui.h".

What bb lib:build does

It runs a compiler invocation defined in bb.edn. On macOS:

cc -O2 -dynamiclib -fPIC -DBUILD_LIBTYPE_SHARED \
   -I vendor \
   -I /opt/homebrew/include \
   -L /opt/homebrew/lib -lraylib \
   -framework CoreVideo -framework IOKit -framework Cocoa -framework OpenGL \
   -o lib/libraygui.dylib vendor/raygui_impl.c

The Homebrew include/lib paths are named explicitly because they aren't on the compiler's default search path. bb lib:check reports whether raylib is installed and whether libraygui is already built, with the fix for each; every example task runs bb lib:build automatically if the library is missing, so a fresh clone needs no separate setup step.

This is the load-bearing part of the whole build, and it fails silently if you get it wrong.

raygui's controls call raylib's own functions internally: GetMousePosition, DrawRectangle, MeasureTextEx, and more, every frame. Those calls must reach the same libraylib instance that the jolt process has already loaded, or raygui reads mouse position and draws geometry against a second, independent copy of raylib's global state, one that never receives input events and never gets flushed to the real framebuffer. The result: every control renders (raygui draws its own chrome) but nothing ever responds to input, because the click landed in the jolt process's libraylib instance while the control checked a different one.

A statically-linked raygui would compile and load without error, and every control would simply be inert. There is no crash, no warning, nothing in the log to point at. Dynamic linkage gives raygui and jolt the same libraylib instance for free, because both resolve the same shared library at load time; -lraylib in the build invocation above links dynamically by default, so getting this right on macOS is a matter of not accidentally adding a static-link flag, not adding one.

Verifying a build

Two checks, both read-only:

nm -gU lib/libraygui.dylib | grep -c ' T _Gui'
# 61

otool -L lib/libraygui.dylib
# lib/libraygui.dylib:
#         lib/libraygui.dylib (compatibility version 0.0.0, current version 0.0.0)
#         /opt/homebrew/opt/raylib/lib/libraylib.600.dylib (compatibility version 600.0.0, current version 6.0.0)
#         /System/Library/Frameworks/CoreVideo.framework/...
#         /System/Library/Frameworks/IOKit.framework/...
#         /System/Library/Frameworks/Cocoa.framework/...
#         /System/Library/Frameworks/OpenGL.framework/...
#         /usr/lib/libSystem.B.dylib

nm -gU reports 61 exported T _Gui* symbols, matching the 61 RAYGUIAPI declarations in vendor/raygui.h. otool -L is the dynamic-link check itself: the second line naming libraylib.600.dylib (not a static blob folded into the binary) is what makes the previous section true. If that line is missing, or points somewhere other than the raylib you expect, the build has silently gone wrong in exactly the way that produces inert controls.

Linux

cc -O2 -shared -fPIC -DBUILD_LIBTYPE_SHARED \
   -I vendor \
   -o lib/libraygui.so vendor/raygui_impl.c \
   -lraylib -lGL -lm -lpthread -ldl -lrt -lX11

These now run in CI on every push, against a raylib 6.0 built from source.

The library flags must come after raygui_impl.c. They used to sit before it, copied from the macOS invocation where order does not matter, and that was wrong in a way worth describing because it fails silently in this project's signature style:

  • GNU ld resolves left to right, and Ubuntu links with --as-needed by default.
  • A -lraylib placed before the translation unit that needs it therefore records no DT_NEEDED entry at all.
  • The build succeeds. The .so appears, and it exports all 61 Gui* symbols, so a symbol count looks perfect.
  • But ldd lib/libraygui.so shows no raylib, raylib's symbols are left undefined with nothing recording where to find them, and jolt then reports required native library raygui not found while pointing at a file that plainly exists.

That error names the wrong thing entirely, which is what makes this expensive to diagnose. apt has no libraylib-dev on Ubuntu 24.04 either (with universe enabled the only match is python3-xraylib, an X-ray physics library), so a source build is the normal path rather than a fallback.

Verified in an ubuntu:24.04 container: 61 Gui* symbols, ldd resolving libraylib.so.600, and bb check compiling all 24 example namespaces.

Bumping the vendored header

  1. Replace vendor/raygui.h with the new revision.
  2. Update the pinned revision string in NOTICE.
  3. bb lib:build to recompile against it.
  4. bb check to confirm every example still compiles against any signature changes.
  5. Re-run and re-screenshot at least scroll-panel, the example most exposed to a layout or style regression (it is the one control taking two by-value Rectangles in a single call), and any example whose control depends on the currently-loaded style once the styling group lands.

See also