BareGit

Fetch and statically link pinned Lua 5.4.9

Build Lua from the verified upstream archive through FetchContent.
Remove the system Lua package requirement from the Arch package.
Author: MetroWind <chris.corsair@gmail.com>
Date: Wed Sep 23 14:41:25 2026 -0700
Commit: c511e8d6da0b6ec4b7d729314006b0874e602404

Changes

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0c84c71..07d63cc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,23 +49,56 @@ find_package(Threads REQUIRED)
 find_package(OpenSSL REQUIRED COMPONENTS Crypto)
 
 if(NETHACK_BUILD_ENGINE)
-    find_path(NETHACK_LUA_INCLUDE_DIR
-        NAMES lua.h
-        PATH_SUFFIXES lua5.4 lua54 lua)
-    find_library(NETHACK_LUA_LIBRARY
-        NAMES lua5.4 lua54 lua)
-    if(NOT NETHACK_LUA_INCLUDE_DIR OR NOT NETHACK_LUA_LIBRARY)
-        message(FATAL_ERROR
-            "NetHack requires Lua 5.4 headers and a linkable library")
-    endif()
-
-    file(STRINGS "${NETHACK_LUA_INCLUDE_DIR}/lua.h"
-        NETHACK_LUA_VERSION_LINE REGEX "^#define LUA_VERSION_NUM")
-    if(NOT NETHACK_LUA_VERSION_LINE MATCHES "504")
-        message(FATAL_ERROR
-            "NetHack requires Lua 5.4 headers; found "
-            "${NETHACK_LUA_INCLUDE_DIR}/lua.h")
-    endif()
+    FetchContent_Declare(
+        lua
+        URL https://www.lua.org/ftp/lua-5.4.9.tar.gz
+        URL_HASH SHA256=2335b6c582a52654f94612bf10d2f4672805d05329aa6568b1d8cd9e5c6fb8e6
+        DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
+    # The upstream archive has no CMakeLists.txt; MakeAvailable fetches it,
+    # and the static library target is defined here.
+    FetchContent_MakeAvailable(lua)
+
+    set(NETHACK_LUA_INCLUDE_DIR "${lua_SOURCE_DIR}/src")
+    add_library(nethack_lua STATIC
+        "${NETHACK_LUA_INCLUDE_DIR}/lapi.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lcode.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lctype.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/ldebug.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/ldo.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/ldump.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lfunc.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lgc.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/llex.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lmem.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lobject.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lopcodes.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lparser.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lstate.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lstring.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/ltable.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/ltm.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lundump.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lvm.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lzio.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lauxlib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lbaselib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lcorolib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/ldblib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/liolib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lmathlib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/loadlib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/loslib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lstrlib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/ltablib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/lutf8lib.c"
+        "${NETHACK_LUA_INCLUDE_DIR}/linit.c")
+    target_compile_features(nethack_lua PRIVATE c_std_99)
+    target_compile_definitions(nethack_lua PRIVATE LUA_USE_LINUX)
+    target_include_directories(nethack_lua PUBLIC
+        "${NETHACK_LUA_INCLUDE_DIR}")
+    target_link_libraries(nethack_lua PUBLIC m ${CMAKE_DL_LIBS})
+    set_target_properties(nethack_lua PROPERTIES
+        POSITION_INDEPENDENT_CODE ON)
 
     FetchContent_Declare(
         nethack
@@ -100,11 +133,13 @@ if(NETHACK_BUILD_ENGINE)
         COMMAND "${CMAKE_COMMAND}"
             "-DNETHACK_SOURCE_DIR=${NETHACK_WORK_DIR}"
             "-DNETHACK_LUA_INCLUDE_DIR=${NETHACK_LUA_INCLUDE_DIR}"
-            "-DNETHACK_LUA_LIBRARY=${NETHACK_LUA_LIBRARY}"
+            "-DNETHACK_LUA_LIBRARY=$<TARGET_FILE:nethack_lua>"
             "-DNETHACK_COMPILER=${CMAKE_C_COMPILER}"
             "-DNETHACK_ARCHIVER=${CMAKE_AR}"
             -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_nethack.cmake"
-        DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_nethack.cmake"
+        DEPENDS
+            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/build_nethack.cmake"
+            nethack_lua
         VERBATIM)
 
     add_custom_target(nethack_engine_build
@@ -115,8 +150,7 @@ if(NETHACK_BUILD_ENGINE)
         IMPORTED_LOCATION "${NETHACK_COMPAT_LIBRARY}"
         INTERFACE_INCLUDE_DIRECTORIES
             "${NETHACK_WORK_DIR}/include"
-        INTERFACE_LINK_LIBRARIES
-            "${NETHACK_LUA_LIBRARY};m;dl")
+        INTERFACE_LINK_LIBRARIES nethack_lua)
     add_dependencies(nethack_lib nethack_engine_build)
 
     add_library(NetHack::libnethack ALIAS nethack_lib)
diff --git a/README.md b/README.md
index 3767dbb..f0fdc8d 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,8 @@ records without receiving gameplay controls.
 
 ## Build
 
-The default build uses system Lua 5.4 headers and library. CMake checks the
-Lua header version before configuring NetHack.
+The default build fetches and statically builds Lua 5.4.9 for the NetHack
+engine, so a system Lua installation is not required.
 
 ```sh
 cmake -S . -B build
diff --git a/cmake/build_nethack.cmake b/cmake/build_nethack.cmake
index c1f0872..fe73a28 100644
--- a/cmake/build_nethack.cmake
+++ b/cmake/build_nethack.cmake
@@ -18,7 +18,7 @@ set(NETHACK_COMMON_ARGS
     "WANT_SYSTEM_LUA=1"
     "LINUX_DISTRO=debian"
     "PKG_EXISTS=yes"
-    "LUA_VERSION=5.4.8"
+    "LUA_VERSION=5.4.9"
     "LUAHEADERS=${NETHACK_LUA_INCLUDE_DIR}"
     "LUACFLAGS=-I${NETHACK_LUA_INCLUDE_DIR}"
     "LUALIBS=${NETHACK_LUA_LIBRARY} -lm -ldl")
@@ -66,12 +66,13 @@ string(REPLACE
     NETHACK_END_CONTENT "${NETHACK_END_CONTENT}")
 file(WRITE "${NETHACK_END_SOURCE}" "${NETHACK_END_CONTENT}")
 
-# The upstream libnh rule names this archive even when system Lua is used.
-# It is a dependency marker only; the executable links the system library.
+# The upstream libnh rule archives this version-named dependency. Keep an
+# empty marker here; the application links the CMake-built Lua library
+# separately because ar stores nested archives as opaque members.
 file(MAKE_DIRECTORY "${NETHACK_SOURCE_DIR}/lib/lua")
 execute_process(
     COMMAND "${NETHACK_ARCHIVER}" rcs
-        "${NETHACK_SOURCE_DIR}/lib/lua/liblua-5.4.8.a"
+        "${NETHACK_SOURCE_DIR}/lib/lua/liblua-5.4.9.a"
     RESULT_VARIABLE archive_result
     OUTPUT_VARIABLE archive_output
     ERROR_VARIABLE archive_error)
diff --git a/designs/design-0-mcp.md b/designs/design-0-mcp.md
index 33aecb8..7d11b28 100644
--- a/designs/design-0-mcp.md
+++ b/designs/design-0-mcp.md
@@ -17,8 +17,8 @@ Confirmed requirements:
   takeover mode.
 - ASCII graphics are sufficient.
 - Fetch NetHack, libmw, and other project dependencies through CMake
-  FetchContent with pinned revisions. Common libraries such as curl and
-  OpenSSL may come from the system; system Lua is also permitted.
+  FetchContent with pinned revisions. Build Lua 5.4.9 through FetchContent;
+  common platform libraries such as curl and OpenSSL may come from the system.
 - This document specifies the design only; it does not authorize
   implementation.
 
@@ -69,12 +69,11 @@ Some input is already structured: text prompts supply a buffer, menus
 return selected identifiers, and extended commands return an index into
 the command table. We should preserve these distinctions.
 
-The native library build has not yet succeeded locally. The initial
-attempt generated ignored build artifacts but failed to find its expected
-Lua dependency. System Lua 5.4.8 exists, but the upstream distribution
-detection does not recognize this Gentoo installation. The library target
-also names a bundled Lua archive explicitly. Therefore system-Lua support
-must be verified as part of implementation, not assumed from its presence.
+The NetHack library build uses the Lua headers and library fetched through
+CMake. The build adapter supplies the versioned archive marker expected by
+NetHack's Linux library rule, then links the separately built Lua library
+into the server. This avoids relying on the host distribution's Lua package
+and avoids nesting a Lua archive inside the NetHack archive.
 
 The [official upstream library documentation](https://github.com/NetHack/NetHack/blob/NetHack-5.0/sys/libnh/README.md)
 is a useful overview. Exact ABI details must come from the pinned fetched
@@ -521,19 +520,13 @@ libmw so its moving-branch declarations do not determine the versions.
 Do not depend on artifacts from another project's `build/` directory.
 
 Common platform libraries, including curl, OpenSSL, and threads, may be
-discovered through CMake package discovery or pkg-config. Prefer system
-Lua 5.4 for this build, requiring both headers and a linkable library.
-Pass the discovered include and library paths into the NetHack build
-adapter explicitly; do not rely on upstream distribution detection or
-hard-code Gentoo-specific paths. Fail configuration with a clear missing
-dependency message if compatible system Lua is unavailable. A future
-bundled Lua option must obtain its sources through FetchContent too.
-
-First validate a minimal worker linked to the actual generated `libnh.a`
-and system Lua. Resolve the system-Lua/archive mismatch in a reproducible
-build adapter or a small patch stored with the application and applied
-only to the isolated build copy. A passing header compilation
-alone is not evidence that the library can start and run a game.
+discovered through CMake package discovery or pkg-config. Keep Lua source
+fetching, its SHA-256 pin, and its static target in the CMake build. Pass the
+fetched header directory and built library path into the NetHack build
+adapter explicitly; do not rely on upstream distribution detection.
+Validate the worker against the generated `libnh.a` and the same Lua build.
+A passing header compilation alone is not evidence that the library can
+start and run a game.
 
 Use a private application data root, configurable on the command line.
 Each game gets a unique directory and a `save/` subdirectory with owner-only
diff --git a/packages/arch/PKGBUILD b/packages/arch/PKGBUILD
index 2555f4c..e3d2d35 100644
--- a/packages/arch/PKGBUILD
+++ b/packages/arch/PKGBUILD
@@ -5,8 +5,8 @@ pkgdesc='Multi-game NetHack server with an MCP interface'
 arch=('x86_64')
 url='https://git.xeno.darksair.org/nethack-mcp.git'
 license=('custom:NetHack' 'MIT')
-depends=('curl' 'lua' 'openssl' 'sqlite' 'systemd')
-makedepends=('cmake' 'git' 'lua')
+depends=('curl' 'openssl' 'sqlite' 'systemd')
+makedepends=('cmake' 'git')
 backup=('etc/nethack-mcp.toml')
 source=(
     'nethack-mcp::git+https://git.xeno.darksair.org/nethack-mcp.git'
@@ -34,7 +34,7 @@ build() {
         -DCMAKE_INSTALL_PREFIX=/usr \
         -DNETHACK_BUILD_ENGINE=ON \
         -Wno-dev
-    cmake --build build
+    cmake --build build -j24
 }
 
 package() {
@@ -59,4 +59,6 @@ package() {
         "$pkgdir/usr/share/licenses/nethack-mcp/NetHack"
     install -Dm644 "$srcdir/build/_deps/tomlplusplus-src/LICENSE" \
         "$pkgdir/usr/share/licenses/nethack-mcp/MIT"
+    install -Dm644 "$srcdir/build/_deps/lua-src/src/lua.h" \
+        "$pkgdir/usr/share/licenses/nethack-mcp/Lua"
 }
diff --git a/packages/arch/README.md b/packages/arch/README.md
index 303f9e5..ede0dbe 100644
--- a/packages/arch/README.md
+++ b/packages/arch/README.md
@@ -8,6 +8,8 @@ makepkg -si
 
 The package builds the pinned NetHack engine and installs its runtime files
 with the server. It does not depend on a separately installed NetHack game.
+The build also fetches and statically builds Lua 5.4.9, so it does not need a
+system Lua package.
 It installs `/etc/nethack-mcp.toml`, creates the `nethack-mcp` service user
 through `sysusers.d`, and provides a systemd unit.
 
diff --git a/src/engine_worker.cpp b/src/engine_worker.cpp
index dd2a745..4061b64 100644
--- a/src/engine_worker.cpp
+++ b/src/engine_worker.cpp
@@ -54,7 +54,7 @@ int runEngineWorker(int argc, char* argv[])
             {"game_id", nullptr},
             {"nethack_commit",
              "c94fd5225beef48143244bfb7bc42682aad58741"},
-            {"lua_version", "5.4.8"},
+            {"lua_version", "5.4.9"},
         };
         std::string error;
         if(!channel.send(hello, error))