Add proper CMake MCRF_HEADLESS option for headless builds

- Add option(MCRF_HEADLESS) to CMakeLists.txt for official headless support
- Conditional compilation: skip ImGui sources when headless
- Conditional linking: no SFML/OpenGL libraries in headless mode
- Auto-define MCRF_HEADLESS preprocessor flag

Verified:
- Zero SFML/OpenGL dynamic dependencies (ldd confirms)
- Python interpreter fully functional in headless mode
- Core mcrfpy types work: Vector, Color, Scene, Frame, Grid
- Binary size: 1.6 MB headless vs 2.5 MB normal (36% reduction)

Build with: cmake .. -DMCRF_HEADLESS=ON

Contributes to #158

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-01-30 23:32:59 -05:00
commit 4c70aee020
2 changed files with 92 additions and 28 deletions

View file

@ -8,6 +8,13 @@ project(McRogueFace)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# Headless build option (no SFML, no graphics - for server/testing/Emscripten prep)
option(MCRF_HEADLESS "Build without graphics dependencies (SFML, ImGui)" OFF)
if(MCRF_HEADLESS)
message(STATUS "Building in HEADLESS mode - no SFML/ImGui dependencies")
endif()
# Detect cross-compilation for Windows (MinGW)
if(CMAKE_CROSSCOMPILING AND WIN32)
set(MCRF_CROSS_WINDOWS TRUE)
@ -36,36 +43,65 @@ else()
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
endif()
# ImGui and ImGui-SFML include directories
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui)
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui-sfml)
# ImGui and ImGui-SFML include directories (not needed in headless mode)
if(NOT MCRF_HEADLESS)
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui)
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui-sfml)
# ImGui source files
set(IMGUI_SOURCES
${CMAKE_SOURCE_DIR}/modules/imgui/imgui.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_draw.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_tables.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_widgets.cpp
${CMAKE_SOURCE_DIR}/modules/imgui-sfml/imgui-SFML.cpp
)
# ImGui source files
set(IMGUI_SOURCES
${CMAKE_SOURCE_DIR}/modules/imgui/imgui.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_draw.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_tables.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_widgets.cpp
${CMAKE_SOURCE_DIR}/modules/imgui-sfml/imgui-SFML.cpp
)
endif()
# Collect all the source files
file(GLOB_RECURSE SOURCES "src/*.cpp")
# Add ImGui sources to the build
list(APPEND SOURCES ${IMGUI_SOURCES})
# Add ImGui sources to the build (only if not headless)
if(NOT MCRF_HEADLESS)
list(APPEND SOURCES ${IMGUI_SOURCES})
endif()
# Find OpenGL (required by ImGui-SFML)
if(MCRF_CROSS_WINDOWS)
# For cross-compilation, OpenGL is provided by MinGW
set(OPENGL_LIBRARIES opengl32)
else()
find_package(OpenGL REQUIRED)
set(OPENGL_LIBRARIES OpenGL::GL)
# Find OpenGL (required by ImGui-SFML) - not needed in headless mode
if(NOT MCRF_HEADLESS)
if(MCRF_CROSS_WINDOWS)
# For cross-compilation, OpenGL is provided by MinGW
set(OPENGL_LIBRARIES opengl32)
else()
find_package(OpenGL REQUIRED)
set(OPENGL_LIBRARIES OpenGL::GL)
endif()
endif()
# Create a list of libraries to link against
if(MCRF_CROSS_WINDOWS)
if(MCRF_HEADLESS)
# Headless build: no SFML, no OpenGL
if(WIN32 OR MCRF_CROSS_WINDOWS)
set(LINK_LIBS
libtcod
python314)
if(MCRF_CROSS_WINDOWS)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/lib)
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows)
else()
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
link_directories(${CMAKE_SOURCE_DIR}/__lib)
endif()
else()
# Unix/Linux headless build
set(LINK_LIBS
tcod
python3.14
m dl util pthread)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
link_directories(${CMAKE_SOURCE_DIR}/__lib)
endif()
elseif(MCRF_CROSS_WINDOWS)
# MinGW cross-compilation: use full library names
set(LINK_LIBS
sfml-graphics
@ -128,6 +164,11 @@ add_executable(mcrogueface ${SOURCES})
# Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code)
target_compile_definitions(mcrogueface PRIVATE NO_SDL)
# Define MCRF_HEADLESS for headless builds (excludes SFML/ImGui code)
if(MCRF_HEADLESS)
target_compile_definitions(mcrogueface PRIVATE MCRF_HEADLESS)
endif()
# On Windows, define Py_ENABLE_SHARED for proper Python DLL imports
# Py_PYCONFIG_H prevents Include/pyconfig.h (Linux config) from being included
# (PC/pyconfig.h already defines HAVE_DECLSPEC_DLL and MS_WINDOWS)

View file

@ -679,9 +679,9 @@ The stub file grew from ~700 lines to ~900 lines with additional types and metho
# Normal SFML build (default)
make
# Headless build (no SFML dependency)
# Headless build (no SFML/ImGui dependencies)
mkdir build-headless && cd build-headless
cmake .. -DCMAKE_CXX_FLAGS="-DMCRF_HEADLESS" -DCMAKE_BUILD_TYPE=Debug
cmake .. -DMCRF_HEADLESS=ON -DCMAKE_BUILD_TYPE=Release
make
```
@ -700,9 +700,32 @@ The libtcod approach of `#ifndef NO_SDL` guards works when **all platform includ
**Total**: ~3 hours for clean headless compilation
### Next Steps
### Completed Milestones
1. **Test Python bindings** - Ensure mcrfpy module loads in headless mode
2. **Add CMake option** - `option(MCRF_HEADLESS "Build without graphics" OFF)`
3. **Link-time validation** - Verify no SFML symbols are referenced
4. **Emscripten testing** - Try building with emcc
1. ✅ **Test Python bindings** - mcrfpy module loads and works in headless mode
- Vector, Color, Scene, Frame, Grid all functional
- libtcod integrations (BSP, pathfinding) available
2. ✅ **Add CMake option** - `option(MCRF_HEADLESS "Build without graphics" OFF)`
- Proper conditional compilation and linking
- No SFML symbols in headless binary
3. ✅ **Link-time validation** - `ldd` confirms zero SFML/OpenGL dependencies
4. ✅ **Binary size reduction** - Headless is 1.6 MB vs 2.5 MB normal build (36% smaller)
### Python Test Results (Headless Mode)
```python
# All these work in headless build:
import mcrfpy
v = mcrfpy.Vector(10, 20) # ✅
c = mcrfpy.Color(255, 128, 64) # ✅
scene = mcrfpy.Scene('test') # ✅
frame = mcrfpy.Frame(pos=(0,0)) # ✅
grid = mcrfpy.Grid(grid_size=(10,10)) # ✅
```
### Remaining Steps for Emscripten
1. **Main loop extraction** - Extract `GameEngine::doFrame()` for callback-based loop
2. **Emscripten toolchain** - Add CMake toolchain file for emcc
3. **VRSFML integration** - Replace stubs with actual WebGL rendering
4. **Python-in-WASM** - Test CPython/Pyodide integration (highest risk)