# CMake build description for xlings — the benchmark's independent control target.
#
# WHY THIS EXISTS. mcpp measuring its own build proves nothing about build
# performance in general: an engine change can be an artefact of one project's
# module graph. xlings is written by different people against a different
# structure (110 module interface units, 46k lines, 6 dependencies), so a result
# that reproduces here is a result about the engine rather than about mcpp.
#
# THE TREE IS PINNED, not vendored: the two code styles live beside this file as
# git submodules (`xlings-2026.8.11.2`, `xlings-2026.8.13.1`). The harness points
# this description at one of them through BENCH_PROJECT_ROOT; by hand:
#
#   cmake -G Ninja -S bench/projects/xlings -B build-xlings \
#         -DXLINGS_ROOT=bench/projects/xlings/xlings-2026.8.13.1 \
#         -DCMAKE_BUILD_TYPE=Release \
#         -DCMAKE_CXX_COMPILER=$HOME/.mcpp/registry/data/xpkgs/xim-x-gcc/16.1.0/bin/g++
#   cmake --build build-xlings
#
# FAIRNESS CONTRACT — the same five as bench/projects/mcpp/CMakeLists.txt:
# same compiler binary, same language flags, same source set, same link output
# kind, same standard library (`import std;`, not a header shim).
#
# THIS ARM USED TO STOP AT THE LINK, and that was recorded as a "known gap". It
# was not a gap — it was unfinished work, and the note saying otherwise let it
# sit. xlings links ftxui, libarchive, lua and mbedtls, all of which arrive in
# mcpp's registry as SOURCE; this description found their headers, compiled all
# 110 units, and then failed on ~1371 undefined `archive_*` / `mbedtls_*` /
# `lua_*`. They are compiled here now and the arm links.
#
# Three things that had looked like boundaries and were not:
#
#   * Transitive headers. Every one is unpacked in mcpp's registry, and they now
#     arrive as the PUBLIC include dirs of the dependency targets below rather
#     than as a hand-written walk of registry subdirectories.
#   * The source packages themselves. Their vendored CMakeLists cannot be used
#     (mbedtls 3.6.1 FATAL_ERRORs unconditionally on a submodule the registry
#     tarball does not carry) and a glob compiles the wrong file set, but every
#     package ships a `.xpkg.lua` naming exactly what mcpp compiles — so
#     xpkg_source_library.cmake builds them from that.
#   * `mcpplibs.xpkg.lua_stdlib`, which is GENERATED by that package's
#     `build.mcpp` rather than checked in. It embeds eleven `.lua` files as
#     strings — small and fully specified, so `embed_lua_stdlib.cmake`
#     reproduces it. "mcpp runs a build program" is not by itself a boundary.

cmake_minimum_required(VERSION 3.30)

# `import std;` is still behind an experimental gate whose key changes with the
# CMake version — this is the CMake 4.4 key. Must be set BEFORE project().
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "f35a9ac6-8463-4d38-8eec-5d6008153e7d")

# Before project() for the same reason as the key above: CMake builds the std
# module through a target it synthesises during the compiler probe inside
# project(), and that target captures CMAKE_CXX_EXTENSIONS as it stands right
# then — default ON. Setting OFF afterwards yields a `gnu++23` std.pcm that no
# `c++23` target can load, and the build fails with `use of undeclared
# identifier 'std'` pointing at the project's own sources. See the identical
# block in ../mcpp/CMakeLists.txt, where this was diagnosed.
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# C IS A SECOND LANGUAGE HERE — libarchive, lua, mbedtls and the five
# compression libraries under libarchive are C, and this arm compiles them from
# source (see the dependency section). `project(xlings CXX)` alone leaves
# CMAKE_C_COMPILER unset and every .c source without a rule.
#
# The harness passes ONLY -DCMAKE_CXX_COMPILER (bench/src/engines/cmake.cppm),
# so the C driver is derived from the C++ one — the same sibling-driver rule
# mcpp applies to route a .c source through the toolchain it was handed. Left
# to CMake, `project(… C …)` would search PATH and find the HOST cc, and the
# hermetic payload in ../common/cmake/hermetic_payload.cmake — which keys off
# CMAKE_CXX_COMPILER — would then point a host gcc at the registry's sysroot.
# That is the two-libc failure its own comment describes, arriving through the
# C half of the build.
if(NOT CMAKE_C_COMPILER AND CMAKE_CXX_COMPILER)
  get_filename_component(_cxx_dir  "${CMAKE_CXX_COMPILER}" DIRECTORY)
  get_filename_component(_cxx_name "${CMAKE_CXX_COMPILER}" NAME)
  string(REGEX REPLACE "clang\\+\\+" "clang" _cc_name "${_cxx_name}")
  if(_cc_name STREQUAL _cxx_name)
    string(REGEX REPLACE "g\\+\\+" "gcc" _cc_name "${_cxx_name}")
  endif()
  if(NOT _cc_name STREQUAL _cxx_name AND EXISTS "${_cxx_dir}/${_cc_name}")
    set(CMAKE_C_COMPILER "${_cxx_dir}/${_cc_name}")
  endif()
endif()

include(${CMAKE_CURRENT_LIST_DIR}/../common/cmake/hermetic_payload.cmake)
bench_hermetic_payload_preproject()

project(xlings C CXX)
set(CMAKE_CXX_MODULE_STD 1)

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()

# ---------------------------------------------------------------------------
# Where the tree is.
# ---------------------------------------------------------------------------
# BENCH_PROJECT_ROOT is what the harness exports for every --project run, and it
# is the reason this description can be shared by both pinned trees. -DXLINGS_ROOT
# and the XLINGS_ROOT environment variable stay supported for driving it by hand.
if(NOT XLINGS_ROOT AND DEFINED ENV{BENCH_PROJECT_ROOT})
  set(XLINGS_ROOT "$ENV{BENCH_PROJECT_ROOT}")
endif()
if(NOT XLINGS_ROOT AND DEFINED ENV{XLINGS_ROOT})
  set(XLINGS_ROOT "$ENV{XLINGS_ROOT}")
endif()
if(NOT XLINGS_ROOT OR NOT EXISTS "${XLINGS_ROOT}/mcpp.toml")
  message(FATAL_ERROR
    "no xlings tree: set -DXLINGS_ROOT=<dir>, or let the bench harness export "
    "BENCH_PROJECT_ROOT via --project. The pinned trees are the submodules "
    "bench/projects/xlings/xlings-<version>/ — run `git submodule update --init`.")
endif()

# ---------------------------------------------------------------------------
# The hermetic payload — SHARED with the mcpp arm. See
# ../common/cmake/hermetic_payload.cmake, including why it is one branch per
# compiler family (gcc gets -B/--sysroot, clang gets an explicit include chain,
# msvc gets nothing because mcpp uses the system Visual Studio too).
# ---------------------------------------------------------------------------
include(${CMAKE_CURRENT_LIST_DIR}/../common/cmake/hermetic_payload.cmake)
bench_hermetic_payload()
bench_registry_xpkgs(MCPP_XPKGS)

# ---------------------------------------------------------------------------
# Source set — xlings' mcpp.toml infers `src/**/*.{cppm,cpp}` and names
# src/main.cpp as the binary's entry point.
#
# BOTH .cppm AND .cpp ARE GLOBBED, and that is what lets ONE description measure
# xlings' two code styles:
#
#   2026.8.11.2   110 .cppm  +  2 .cpp   — each interface unit carries its own
#                                          implementation
#   2026.8.13.1   110 .cppm  + 92 .cpp   — interface and implementation split
#
# Same module graph, same 46k lines, opposite answers to "where does the code
# live" — which is exactly the `modules` vs `modules-impl` axis the generated
# fixture has, on a real tree. A glob spans both because it is the same rule
# mcpp itself infers from; branching on the style (or carrying two description
# files, or an env var) would be one more thing that can differ between the arms
# for a reason that is not the engine.
#
# It also matters that this is not `main.cpp` alone: against the split style
# that description compiles the interfaces, links nothing, and reports a number
# for a build that never happened.
# ---------------------------------------------------------------------------
file(GLOB_RECURSE XLINGS_MODULES CONFIGURE_DEPENDS "${XLINGS_ROOT}/src/*.cppm")
file(GLOB_RECURSE XLINGS_SOURCES CONFIGURE_DEPENDS "${XLINGS_ROOT}/src/*.cpp")
list(LENGTH XLINGS_MODULES XLINGS_MODULE_COUNT)
list(LENGTH XLINGS_SOURCES XLINGS_SOURCE_COUNT)
if(XLINGS_MODULE_COUNT EQUAL 0)
  message(FATAL_ERROR "no module interface units under ${XLINGS_ROOT}/src")
endif()
if(XLINGS_SOURCE_COUNT EQUAL 0)
  message(FATAL_ERROR "no .cpp under ${XLINGS_ROOT}/src — not even main.cpp; "
                      "is XLINGS_ROOT pointing at a checkout?")
endif()

add_executable(xlings ${XLINGS_SOURCES})
target_sources(xlings
  PRIVATE FILE_SET CXX_MODULES BASE_DIRS "${XLINGS_ROOT}/src" FILES ${XLINGS_MODULES})

# `[build] include_dirs = ["src/libs/json"]` — src/libs/json.cppm reaches for
# <json.hpp> from its global module fragment.
target_include_directories(xlings PRIVATE "${XLINGS_ROOT}/src/libs/json")
# `[build] cxxflags`
target_compile_definitions(xlings PRIVATE LIBARCHIVE_STATIC UNICODE _UNICODE)

# ---------------------------------------------------------------------------
# Dependencies.
#
# The four mcpplibs packages ship SOURCE and are compiled from it — each needs
# its own FILE_SET, because a CXX_MODULES set requires every file to live under
# one of its base directories and these sit in the registry outside the tree.
#
# Versions are PINNED to xlings' mcpp.toml. Newer ones are usually also unpacked
# in the registry, and taking the newest would mean the two arms compile
# different code — a benchmark whose fairness rests on directory ordering is not
# a benchmark.
#
# mcpp stages prebuilt objects for these out of its global cache while cmake
# compiles them from source: a handicap on cmake's cold build, declared here
# rather than hidden.
# ---------------------------------------------------------------------------

bench_add_source_dep(xlings mcpplibs-x-cmdline   0.0.2)
bench_add_source_dep(xlings mcpplibs-x-xpkg      0.0.57)

# `mcpplibs.xpkg.lua_stdlib` is generated, not checked in — libxpkg's build.mcpp
# embeds eleven .lua files as strings. Reproduced here so both arms compile the
# same set of translation units; see embed_lua_stdlib.cmake, which DERIVES the
# set from the directory rather than carrying a copy of it — the copy drifted
# once, and the loss surfaced three files away in a consumer.
file(GLOB xpkg_vers "${MCPP_XPKGS}/mcpplibs-x-xpkg/0.0.57/*")
foreach(d IN LISTS xpkg_vers)
  if(IS_DIRECTORY "${d}/src/lua-stdlib")
    set(XPKG_PKG_ROOT "${d}")
  endif()
endforeach()
if(XPKG_PKG_ROOT)
  set(LUA_STDLIB_CPPM "${CMAKE_CURRENT_BINARY_DIR}/generated/xpkg-lua-stdlib.cppm")
  file(GLOB_RECURSE LUA_STDLIB_SOURCES "${XPKG_PKG_ROOT}/src/lua-stdlib/*.lua")
  add_custom_command(
    OUTPUT "${LUA_STDLIB_CPPM}"
    COMMAND "${CMAKE_COMMAND}" -DXPKG_ROOT=${XPKG_PKG_ROOT} -DOUT=${LUA_STDLIB_CPPM}
            -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_lua_stdlib.cmake"
    DEPENDS ${LUA_STDLIB_SOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/embed_lua_stdlib.cmake"
    COMMENT "Embedding libxpkg's lua-stdlib")
  target_sources(xlings PRIVATE
    FILE_SET fs_lua_stdlib TYPE CXX_MODULES
      BASE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/generated"
      FILES "${LUA_STDLIB_CPPM}")
endif()
bench_add_source_dep(xlings mcpplibs-x-tinyhttps 0.2.9)
bench_add_source_dep(xlings mcpplibs.capi-x-lua  0.0.3)

# ---------------------------------------------------------------------------
# The C/C++ libraries xlings links against.
#
# THIS IS WHAT USED TO STOP THE ARM AT THE LINK. ftxui, libarchive, lua and
# mbedtls arrive in mcpp's registry as SOURCE, and mcpp compiles them — so this
# description found their headers, compiled all 110 units, and then failed with
# ~1371 undefined `archive_*` / `mbedtls_*` / `lua_*`. It was recorded as a
# known gap; it is not one, it was unfinished work.
#
# EACH IS BUILT FROM ITS OWN `.xpkg.lua`, not from the vendored CMakeLists and
# not from a glob — see xpkg_source_library.cmake for the two attempts that
# failed and why the manifest is the only description that makes this arm
# compile the same files mcpp does.
#
# THE LIST IS TRANSITIVE, and it is written out rather than discovered because
# the discovery is what mcpp's package manager does: xlings names 6 direct
# dependencies, and wiring the source ones in surfaced two more headers
# (`mbedtls/ssl.h` for tinyhttps, `lua.h` for capi.lua) and then libarchive's
# own five. Naming them keeps this description honest about what it is — a
# hand-maintained copy of a resolved dependency set, which is exactly why
# bench/projects/ carries a description only for trees this repository can keep
# correct. The VERSIONS are the pins, from each manifest's `deps`.
#
# Both engines compile these from source on a cold build, which is what makes
# the comparison fair. mcpp may serve them from its global build cache instead —
# that asymmetry is declared in ../../README.md §5 rather than hidden here.
# ---------------------------------------------------------------------------
include(${CMAKE_CURRENT_LIST_DIR}/xpkg_source_library.cmake)

bench_add_xpkg_library(bench_ftxui      compat-x-ftxui      6.1.9)
bench_add_xpkg_library(bench_libarchive compat-x-libarchive 3.8.7)
bench_add_xpkg_library(bench_mbedtls    compat-x-mbedtls    3.6.1)
bench_add_xpkg_library(bench_lua        compat-x-lua        5.4.7)

# libarchive's manifest names five dependencies and its generated config header
# turns each of them ON (`#define HAVE_LIBZ 1`, HAVE_LIBLZMA, …). Those defines
# are not optional decoration: they are what makes archive_read_support_filter_*
# call into zlib/bzip2/lz4/zstd/liblzma. libbench_libarchive.a leaves 72 distinct
# such symbols undefined, and nothing in that link error points at the manifest
# that asked for them.
bench_add_xpkg_library(bench_zlib  compat-x-zlib  1.3.2)
bench_add_xpkg_library(bench_bzip2 compat-x-bzip2 1.0.8)
bench_add_xpkg_library(bench_lz4   compat-x-lz4   1.10.0)
bench_add_xpkg_library(bench_zstd  compat-x-zstd  1.5.7)
bench_add_xpkg_library(bench_xz    compat-x-xz    5.8.3)
target_link_libraries(bench_libarchive
  PUBLIC bench_zlib bench_bzip2 bench_lz4 bench_zstd bench_xz)

# PUBLIC on the libraries above carries their include dirs here too, which is
# what replaced a hand-written walk of registry subdirectories: xlings' own
# units reach for <archive.h> and <ftxui/component/event.hpp>, and the mcpplibs
# module units compiled INTO this target reach for <mbedtls/ssl.h> and <lua.h>.
target_link_libraries(xlings PRIVATE
  bench_ftxui bench_libarchive bench_mbedtls bench_lua)

target_link_options(xlings PRIVATE -static-libstdc++)

message(STATUS "xlings: ${XLINGS_MODULE_COUNT} module interface units + "
               "${XLINGS_SOURCE_COUNT} .cpp from ${XLINGS_ROOT}")
