McRogueFace/CMakeLists.txt
John McCardle 9176dca055 Add mcrf-init.sh: game project scaffolding without engine recompilation
New workflow for game developers: run mcrf-init to create a project
directory with symlinks to a pre-built engine, then just write Python
scripts and assets. Games package for distribution (Linux/Windows/WASM)
without ever rebuilding the engine.

mcrf-init.sh creates:
- build/ with symlinked binary and libs, game content in assets/ + scripts/
- build-windows/ (if engine has a Windows build)
- Makefile with run, wasm, dist-linux, dist-windows, dist-wasm targets
- Starter game.py, .gitignore, pyrightconfig.json, VERSION file

CMakeLists.txt: WASM preload paths (assets, scripts) are now
configurable via MCRF_ASSETS_DIR / MCRF_SCRIPTS_DIR cache variables,
so game project Makefiles can point WASM builds at their own content
without modifying the engine.

Also adds pyrightconfig.json for the engine repo itself (IDE support
via stubs/).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 23:17:59 -05:00

421 lines
17 KiB
CMake

# Minimum version of CMake required
cmake_minimum_required(VERSION 3.14)
# Project name
project(McRogueFace)
# Specify the C++ standard
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)
# SDL2 backend option (SDL2 + OpenGL ES 2 - for Emscripten/WebGL, Android, cross-platform)
option(MCRF_SDL2 "Build with SDL2+OpenGL ES 2 backend instead of SFML" OFF)
# Playground mode - minimal scripts for web playground (REPL-focused)
option(MCRF_PLAYGROUND "Build with minimal playground scripts instead of full game" OFF)
# Game shell mode - fullscreen canvas, no REPL chrome (for itch.io / standalone web games)
option(MCRF_GAME_SHELL "Use minimal game-only HTML shell (no REPL)" OFF)
# Emscripten builds: use SDL2 if specified, otherwise fall back to headless
if(EMSCRIPTEN)
if(MCRF_SDL2)
message(STATUS "Emscripten detected - using SDL2 backend")
set(MCRF_HEADLESS OFF)
else()
set(MCRF_HEADLESS ON)
message(STATUS "Emscripten detected - forcing HEADLESS mode (use -DMCRF_SDL2=ON for graphics)")
endif()
endif()
if(MCRF_SDL2)
message(STATUS "Building with SDL2 backend - SDL2+OpenGL ES 2")
endif()
if(MCRF_PLAYGROUND)
message(STATUS "Building in PLAYGROUND mode - minimal scripts for web REPL")
endif()
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)
message(STATUS "Cross-compiling for Windows using MinGW")
endif()
# Add include directories
include_directories(${CMAKE_SOURCE_DIR}/deps)
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/deps/libtcod)
include_directories(${CMAKE_SOURCE_DIR}/src)
include_directories(${CMAKE_SOURCE_DIR}/src/3d)
include_directories(${CMAKE_SOURCE_DIR}/src/platform)
include_directories(${CMAKE_SOURCE_DIR}/src/tiled)
include_directories(${CMAKE_SOURCE_DIR}/src/ldtk)
include_directories(${CMAKE_SOURCE_DIR}/src/audio)
include_directories(${CMAKE_SOURCE_DIR}/modules/RapidXML)
include_directories(${CMAKE_SOURCE_DIR}/modules/json/single_include)
# Python includes: use different paths for Windows vs Linux vs Emscripten
if(EMSCRIPTEN)
# Emscripten build: use Python headers compiled for wasm32-emscripten
# The pyconfig.h from cross-build has correct LONG_BIT and other settings
set(PYTHON_WASM_BUILD "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/build/python")
# Force-include wasm pyconfig.h BEFORE anything else to set correct platform defines
add_compile_options(-include ${PYTHON_WASM_BUILD}/pyconfig.h)
# Override LONG_BIT - Emscripten's limits.h incorrectly defines it as 64 for wasm32
add_compile_definitions(LONG_BIT=32)
# Include wasm build directory FIRST so its pyconfig.h is found by #include "pyconfig.h"
include_directories(BEFORE ${PYTHON_WASM_BUILD})
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include)
message(STATUS "Using Emscripten Python from: ${PYTHON_WASM_BUILD}")
elseif(MCRF_CROSS_WINDOWS)
# Windows cross-compilation: use cpython headers with PC/pyconfig.h
# Problem: Python.h uses #include "pyconfig.h" which finds Include/pyconfig.h (Linux) first
# Solution: Use -include to force Windows pyconfig.h to be included first
# This defines MS_WINDOWS before Python.h is processed, ensuring correct struct layouts
add_compile_options(-include ${CMAKE_SOURCE_DIR}/deps/cpython/PC/pyconfig.h)
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include)
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/PC) # For other Windows-specific headers
# Also include SFML and libtcod Windows headers
include_directories(${CMAKE_SOURCE_DIR}/__lib_windows/sfml/include)
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/include)
else()
# Native builds (Linux/Windows): use existing Python setup
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
endif()
# ImGui and ImGui-SFML include directories (not needed in headless or SDL2 mode)
# SDL2 builds will use ImGui with SDL2 backend later; for now, no ImGui
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
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
)
endif()
# Collect all the source files
file(GLOB_RECURSE SOURCES "src/*.cpp")
# Add ImGui sources to the build (only if using SFML)
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
list(APPEND SOURCES ${IMGUI_SOURCES})
# Add GLAD for OpenGL function loading (needed for 3D rendering on SFML)
list(APPEND SOURCES "${CMAKE_SOURCE_DIR}/src/3d/glad.c")
endif()
# Find OpenGL (required by ImGui-SFML) - not needed in headless mode
# SDL2 builds handle OpenGL ES 2 differently (via SDL2 or Emscripten)
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
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(EMSCRIPTEN)
# Emscripten build: link against WASM-compiled Python and libtcod
set(PYTHON_WASM_BUILD "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/build/python")
set(PYTHON_WASM_PREFIX "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/prefix")
set(LIBTCOD_WASM_BUILD "${CMAKE_SOURCE_DIR}/modules/libtcod-headless/build-emscripten")
# Collect HACL crypto object files (not included in libpython3.14.a)
file(GLOB PYTHON_HACL_OBJECTS "${PYTHON_WASM_BUILD}/Modules/_hacl/*.o")
set(LINK_LIBS
${PYTHON_WASM_BUILD}/libpython3.14.a
${PYTHON_HACL_OBJECTS}
${PYTHON_WASM_BUILD}/Modules/expat/libexpat.a
${PYTHON_WASM_PREFIX}/lib/libmpdec.a
${PYTHON_WASM_PREFIX}/lib/libffi.a
${LIBTCOD_WASM_BUILD}/libtcod.a
${LIBTCOD_WASM_BUILD}/_deps/lodepng-c-build/liblodepng-c.a
${LIBTCOD_WASM_BUILD}/_deps/utf8proc-build/libutf8proc.a)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux) # Use Linux platform stubs for now
# For SDL2 builds, add stb headers for image/font loading
if(MCRF_SDL2)
include_directories(${CMAKE_SOURCE_DIR}/deps/stb)
endif()
message(STATUS "Linking Emscripten Python: ${PYTHON_WASM_BUILD}/libpython3.14.a")
message(STATUS "Linking Emscripten libtcod: ${LIBTCOD_WASM_BUILD}/libtcod.a")
elseif(MCRF_SDL2)
# SDL2 build (non-Emscripten): link against SDL2 and system libraries
# Note: For desktop SDL2 builds in the future
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
set(LINK_LIBS
SDL2::SDL2
OpenGL::GL
tcod
python3.14
m dl util pthread)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
include_directories(${CMAKE_SOURCE_DIR}/deps/stb) # stb_image.h, stb_truetype.h
link_directories(${CMAKE_SOURCE_DIR}/__lib)
message(STATUS "Building with SDL2 backend (desktop)")
elseif(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
sfml-window
sfml-system
sfml-audio
libtcod
python314
${OPENGL_LIBRARIES})
# Add Windows system libraries needed by SFML and MinGW
list(APPEND LINK_LIBS
winmm # Windows multimedia (for audio)
gdi32 # Graphics Device Interface
ws2_32 # Winsock (networking, used by some deps)
ole32 # OLE support
oleaut32 # OLE automation
uuid # UUID library
comdlg32 # Common dialogs
imm32 # Input Method Manager
version # Version info
)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
# Link directories for cross-compiled Windows libs
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/sfml/lib)
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/lib)
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows)
elseif(WIN32)
# Native Windows build (MSVC)
set(LINK_LIBS
sfml-graphics
sfml-window
sfml-system
sfml-audio
tcod
python314
${OPENGL_LIBRARIES})
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
link_directories(${CMAKE_SOURCE_DIR}/__lib)
else()
# Unix/Linux build
set(LINK_LIBS
sfml-graphics
sfml-window
sfml-system
sfml-audio
tcod
python3.14
m dl util pthread
${OPENGL_LIBRARIES})
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
link_directories(${CMAKE_SOURCE_DIR}/__lib)
endif()
# Define the executable target before linking libraries
add_executable(mcrogueface ${SOURCES})
# Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code)
# We ALWAYS need this because libtcod headers expect SDL3, not SDL2
# Our SDL2 backend is separate from libtcod's SDL3 renderer
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()
# Define MCRF_SDL2 for SDL2 builds (uses SDL2+OpenGL ES 2 instead of SFML)
if(MCRF_SDL2)
target_compile_definitions(mcrogueface PRIVATE MCRF_SDL2)
endif()
# Asset/script directories for WASM preloading (game projects override these)
set(MCRF_ASSETS_DIR "${CMAKE_SOURCE_DIR}/assets" CACHE PATH "Assets directory for WASM preloading")
set(MCRF_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/src/scripts" CACHE PATH "Scripts directory for WASM preloading")
set(MCRF_SCRIPTS_PLAYGROUND_DIR "${CMAKE_SOURCE_DIR}/src/scripts_playground" CACHE PATH "Playground scripts for WASM")
# Emscripten-specific link options (use ports for zlib, bzip2, sqlite3)
if(EMSCRIPTEN)
# Base Emscripten options
set(EMSCRIPTEN_LINK_OPTIONS
-sUSE_ZLIB=1
-sUSE_BZIP2=1
-sUSE_SQLITE3=1
-sALLOW_MEMORY_GROWTH=1
-sSTACK_SIZE=2097152
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,FS
-sEXPORTED_FUNCTIONS=_main,_run_python_string,_run_python_string_with_output,_reset_python_environment,_notify_canvas_resize
-sASSERTIONS=2
-sSTACK_OVERFLOW_CHECK=2
-fexceptions
-sNO_DISABLE_EXCEPTION_CATCHING
# Disable features that require dynamic linking support
-sERROR_ON_UNDEFINED_SYMBOLS=0
-sALLOW_UNIMPLEMENTED_SYSCALLS=1
# Preload Python stdlib into virtual filesystem at /lib/python3.14
--preload-file=${CMAKE_SOURCE_DIR}/wasm_stdlib/lib@/lib
# Preload game scripts into /scripts (use playground scripts if MCRF_PLAYGROUND is set)
--preload-file=$<IF:$<BOOL:${MCRF_PLAYGROUND}>,${MCRF_SCRIPTS_PLAYGROUND_DIR},${MCRF_SCRIPTS_DIR}>@/scripts
# Preload assets
--preload-file=${MCRF_ASSETS_DIR}@/assets
# Use custom HTML shell - game shell (fullscreen) or playground shell (REPL)
--shell-file=${CMAKE_SOURCE_DIR}/src/$<IF:$<BOOL:${MCRF_GAME_SHELL}>,shell_game.html,shell.html>
# Pre-JS to fix browser zoom causing undefined values in events
--pre-js=${CMAKE_SOURCE_DIR}/src/emscripten_pre.js
)
# Add SDL2 options if using SDL2 backend
if(MCRF_SDL2)
list(APPEND EMSCRIPTEN_LINK_OPTIONS
-sUSE_SDL=2
-sUSE_SDL_MIXER=2
-sFULL_ES2=1
-sMIN_WEBGL_VERSION=2
-sMAX_WEBGL_VERSION=2
-sUSE_FREETYPE=1
)
# SDL2, SDL2_mixer, and FreeType flags are also needed at compile time for headers
target_compile_options(mcrogueface PRIVATE
-sUSE_SDL=2
-sUSE_SDL_MIXER=2
-sUSE_FREETYPE=1
)
message(STATUS "Emscripten SDL2 options enabled: -sUSE_SDL=2 -sUSE_SDL_MIXER=2 -sFULL_ES2=1 -sUSE_FREETYPE=1")
endif()
target_link_options(mcrogueface PRIVATE ${EMSCRIPTEN_LINK_OPTIONS})
# Output as HTML to use the shell file
set_target_properties(mcrogueface PROPERTIES SUFFIX ".html")
# Set Python home for the embedded interpreter
target_compile_definitions(mcrogueface PRIVATE
MCRF_WASM_PYTHON_HOME="/lib/python3.14"
)
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)
if(WIN32 OR MCRF_CROSS_WINDOWS)
target_compile_definitions(mcrogueface PRIVATE Py_ENABLE_SHARED Py_PYCONFIG_H)
endif()
# On Windows, set subsystem to WINDOWS to hide console (release builds only)
# Use -DMCRF_WINDOWS_CONSOLE=ON for debug builds with console output
option(MCRF_WINDOWS_CONSOLE "Keep console window visible for debugging" OFF)
if(WIN32 AND NOT MCRF_CROSS_WINDOWS)
# MSVC-specific flags
if(NOT MCRF_WINDOWS_CONSOLE)
set_target_properties(mcrogueface PROPERTIES
WIN32_EXECUTABLE TRUE
LINK_FLAGS "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
endif()
elseif(MCRF_CROSS_WINDOWS)
# MinGW cross-compilation
if(NOT MCRF_WINDOWS_CONSOLE)
# Release: use -mwindows to hide console
set_target_properties(mcrogueface PROPERTIES
WIN32_EXECUTABLE TRUE
LINK_FLAGS "-mwindows")
else()
# Debug: keep console for stdout/stderr output
message(STATUS "Windows console enabled for debugging")
endif()
endif()
# Now the linker will find the libraries in the specified directory
target_link_libraries(mcrogueface ${LINK_LIBS})
# Copy assets to build directory post-build
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets $<TARGET_FILE_DIR:mcrogueface>/assets)
# Copy Python scripts to build directory post-build
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/src/scripts $<TARGET_FILE_DIR:mcrogueface>/scripts)
# Copy Python standard library to build directory
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
# On Windows, copy DLLs to executable directory
if(MCRF_CROSS_WINDOWS)
# Cross-compilation: copy DLLs from __lib_windows
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib_windows/sfml/bin $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/bin $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/python314.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/python3.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140_1.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
/usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E echo "Copied Windows DLLs to executable directory")
# Copy Python standard library zip
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/python314.zip $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E echo "Copied Python stdlib")
elseif(WIN32)
# Native Windows build: copy DLLs from __lib
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E echo "Copied DLLs to executable directory")
endif()
# rpath for including shared libraries (Linux/Unix only)
if(NOT WIN32)
set_target_properties(mcrogueface PROPERTIES
INSTALL_RPATH "$ORIGIN/./lib")
endif()