singe/cmake/Superbuild.cmake
2026-09-13 23:48:15 -05:00

483 lines
25 KiB
CMake

#
# Singe 3
# Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public Licens
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
# Superbuild: builds every vendored library into BUILD_DIR, then Singe against them.
# Entered from CMakeLists.txt when SINGE_SUPERBUILD is ON (the presets set it). Each
# library is an ExternalProject with its own configure, build and install steps, so a
# change to one rebuilds only it and what depends on it:
# cmake --build --preset linux-x86_64 --target rebuild-ffmpeg
# The toolchain environment (PATH, SYSROOT, osxcross variables) comes from
# ../toolchains/toolchains.sh; build-all.sh sources it before calling cmake.
include(ExternalProject)
include(ProcessorCount)
ProcessorCount(SB_JOBS)
if(SB_JOBS EQUAL 0)
set(SB_JOBS 4)
endif()
# Target description: the presets pass these; a plain cmake invocation can take them from
# the environment toolchains.sh exports.
foreach(pair "SINGE_TRIPLE;TRIPLE" "SINGE_CROSS_OS;CROSS_OS" "SINGE_SUFFIX;SUFFIX")
list(GET pair 0 var)
list(GET pair 1 env)
if(NOT DEFINED ${var})
set(${var} "$ENV{${env}}")
endif()
endforeach()
if(NOT SINGE_TRIPLE OR NOT SINGE_CROSS_OS)
message(FATAL_ERROR "SINGE_TRIPLE and SINGE_CROSS_OS must be set (use a preset or source toolchains.sh).")
endif()
set(SB_PREFIX ${BUILD_DIR})
set(SB_THIRDPARTY ${CMAKE_SOURCE_DIR}/thirdparty)
file(MAKE_DIRECTORY ${SB_PREFIX}/include ${SB_PREFIX}/lib)
# Compilers as the toolchain resolved them; the configure based libraries get them by name.
set(SB_CC "${CMAKE_C_COMPILER}")
if(CMAKE_CXX_COMPILER)
set(SB_CXX "${CMAKE_CXX_COMPILER}")
elseif(DEFINED ENV{CXX})
set(SB_CXX "$ENV{CXX}")
else()
string(REGEX REPLACE "gcc$" "g++" SB_CXX "${SB_CC}")
string(REGEX REPLACE "clang$" "clang++" SB_CXX "${SB_CXX}")
endif()
if(CMAKE_RANLIB)
set(SB_RANLIB "${CMAKE_RANLIB}")
elseif(DEFINED ENV{RANLIB})
set(SB_RANLIB "$ENV{RANLIB}")
else()
set(SB_RANLIB ranlib)
endif()
# The flags build-all.sh exported for every library: our install tree first, then the
# platform's. The Pi sysroot and the osxcross linker come from the toolchain.
set(SB_CFLAGS_BASE "$ENV{CFLAGS}")
set(SB_LDFLAGS "-L${SB_PREFIX}/lib $ENV{LDFLAGS}")
if(CMAKE_SYSROOT)
set(SB_CFLAGS_BASE "--sysroot=${CMAKE_SYSROOT} ${SB_CFLAGS_BASE}")
endif()
set(SB_CFLAGS "-I${SB_PREFIX}/include ${SB_CFLAGS_BASE}")
set(SB_PKG_CONFIG_LIBDIR "${SB_PREFIX}/lib/pkgconfig")
# A cross build looks inside its sysroot; a build for this machine looks at this machine. The
# sysroot is what tells them apart, not the platform's name.
if(SINGE_SYSROOT)
set(SB_PKG_CONFIG_LIBDIR "${SB_PKG_CONFIG_LIBDIR}:${SINGE_SYSROOT}/usr/lib/${SINGE_TRIPLE}/pkgconfig:${SINGE_SYSROOT}/usr/lib/pkgconfig:${SINGE_SYSROOT}/usr/share/pkgconfig")
elseif(KANGAROO_OS STREQUAL "linux")
set(SB_PKG_CONFIG_LIBDIR "${SB_PKG_CONFIG_LIBDIR}:/usr/lib/${SINGE_TRIPLE}/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig")
elseif(DEFINED ENV{PKG_CONFIG_LIBDIR})
set(SB_PKG_CONFIG_LIBDIR "${SB_PKG_CONFIG_LIBDIR}:$ENV{PKG_CONFIG_LIBDIR}")
endif()
# Environment every step runs under. CFLAGS is a parameter because SDL3_image's vendored
# libpng must not see the zlib headers under the install prefix.
function(singeStepEnv outVar cflags)
set(env ${CMAKE_COMMAND} -E env
"CC=${SB_CC}"
"CXX=${SB_CXX}"
"RANLIB=${SB_RANLIB}"
"CFLAGS=${cflags}"
"CXXFLAGS=${cflags}"
"LDFLAGS=${SB_LDFLAGS}"
"LD_LIBRARY_PATH=${SB_PREFIX}/lib"
"PKG_CONFIG_LIBDIR=${SB_PKG_CONFIG_LIBDIR}"
"MAKEFLAGS=-j${SB_JOBS}"
)
if(CMAKE_AR)
list(APPEND env "AR=${CMAKE_AR}")
endif()
if(SINGE_SYSROOT)
list(APPEND env "PKG_CONFIG_SYSROOT_DIR=${SINGE_SYSROOT}")
endif()
set(${outVar} ${env} PARENT_SCOPE)
endfunction()
singeStepEnv(SB_ENV "${SB_CFLAGS}")
singeStepEnv(SB_ENV_NO_PREFIX_INCLUDE "${SB_CFLAGS_BASE}")
set(SB_CMAKE_ARGS
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_INSTALL_PREFIX=${SB_PREFIX}
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
)
# A vendored CMake project: name, source directory, its dependencies, extra cache arguments,
# and the step environment to use.
function(singeCmakeProject name source deps args env)
set(binary ${SB_PREFIX}/build/${name})
ExternalProject_Add(${name}
SOURCE_DIR ${source}
BINARY_DIR ${binary}
DEPENDS ${deps}
CONFIGURE_COMMAND ${env} ${CMAKE_COMMAND} -S ${source} -B ${binary} ${SB_CMAKE_ARGS} ${args}
BUILD_COMMAND ${env} ${CMAKE_COMMAND} --build ${binary} --parallel ${SB_JOBS}
INSTALL_COMMAND ${env} ${CMAKE_COMMAND} --build ${binary} --target install
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON
)
singeRebuildTarget(${name})
endfunction()
# rebuild-<name> forgets the stamps and the build directory and builds the library again, then whatever depends on it
# is rebuilt by the normal build.
function(singeRebuildTarget name)
ExternalProject_Get_Property(${name} STAMP_DIR BINARY_DIR)
add_custom_target(rebuild-${name}
COMMAND ${CMAKE_COMMAND} -E rm -rf ${STAMP_DIR} ${BINARY_DIR}
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${name}
COMMENT "Rebuilding ${name}"
)
endfunction()
# ===== zlib (configure script; the generated zconf.h lives in the build directory) =====
set(zlibBinary ${SB_PREFIX}/build/zlib)
ExternalProject_Add(zlib
SOURCE_DIR ${SB_THIRDPARTY}/zlib
BINARY_DIR ${zlibBinary}
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env "CC=${SB_CC}" "AR=${CMAKE_AR}" "RANLIB=${SB_RANLIB}" "CFLAGS=-I${zlibBinary} ${SB_CFLAGS_BASE}" ${SB_THIRDPARTY}/zlib/configure --prefix=${SB_PREFIX} --static
BUILD_COMMAND ${SB_ENV} make
INSTALL_COMMAND ${SB_ENV} make install
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON
)
singeRebuildTarget(zlib)
# ===== zstd =====
singeCmakeProject(zstd ${SB_THIRDPARTY}/zstd/build/cmake "" "-DZSTD_BUILD_SHARED=off;-DZSTD_BUILD_STATIC=on;-DZSTD_BUILD_PROGRAMS=off" "${SB_ENV}")
# ===== SDL3 and its satellites =====
singeCmakeProject(SDL3 ${SB_THIRDPARTY}/SDL3 "" "-DSDL_SHARED=off;-DSDL_STATIC=on;-DSDL_TESTS=off;-DSDL_EXAMPLES=off" "${SB_ENV}")
# ===== Host tools: the shader compiler (see cmake/hostTools.cmake) =====
include(${CMAKE_CURRENT_LIST_DIR}/hostTools.cmake)
# ===== Jolt Physics (C++; static, single precision, no tests or samples) =====
# The x86 baseline is SSE4.1/4.2 so a shipped binary runs on anything from 2008 on; AVX and the
# bit-count instructions would silently raise it. No link-time optimisation through the cross
# compiler, no warnings-as-errors with a clang Jolt has not met, nothing but the library; the GPU
# compute paths (hair and soft bodies on DX12, Vulkan, Metal or a CPU fallback) would need a shader
# compiler at build time and are not used.
set(joltOptions
-DTARGET_UNIT_TESTS=OFF -DTARGET_HELLO_WORLD=OFF -DTARGET_PERFORMANCE_TEST=OFF -DTARGET_SAMPLES=OFF -DTARGET_VIEWER=OFF
-DJPH_BUILD_SHARED_LIBS=OFF -DDOUBLE_PRECISION=OFF -DCROSS_PLATFORM_DETERMINISTIC=OFF
-DUSE_SSE4_1=ON -DUSE_SSE4_2=ON -DUSE_AVX=OFF -DUSE_AVX2=OFF -DUSE_AVX512=OFF -DUSE_F16C=OFF -DUSE_FMADD=OFF -DUSE_LZCNT=OFF -DUSE_TZCNT=OFF
-DINTERPROCEDURAL_OPTIMIZATION=OFF -DENABLE_ALL_WARNINGS=OFF -DGENERATE_DEBUG_SYMBOLS=OFF
-DDEBUG_RENDERER_IN_DEBUG_AND_RELEASE=OFF -DDEBUG_RENDERER_IN_DISTRIBUTION=ON -DFLOATING_POINT_EXCEPTIONS_ENABLED=OFF -DCPP_EXCEPTIONS_ENABLED=OFF -DCPP_RTTI_ENABLED=OFF
-DJPH_USE_DX12=OFF -DJPH_USE_VK=OFF -DJPH_USE_MTL=OFF -DJPH_USE_CPU_COMPUTE=OFF
)
singeCmakeProject(Jolt ${SB_THIRDPARTY}/JoltPhysics/Build "" "${joltOptions}" "${SB_ENV}")
# ===== Recast Navigation (C++; static, the libraries only) =====
singeCmakeProject(recastnavigation ${SB_THIRDPARTY}/recastnavigation ""
"-DRECASTNAVIGATION_DEMO=OFF;-DRECASTNAVIGATION_TESTS=OFF;-DRECASTNAVIGATION_EXAMPLES=OFF;-DBUILD_SHARED_LIBS=OFF"
"${SB_ENV}")
set(sdl3Dir -DSDL3_DIR=${SB_PREFIX}/lib/cmake/SDL3)
# Brotli, for the WOFF2 web fonts FreeType reads only when it is present. It is built before
# SDL3_image and SDL3_ttf so that it is in the prefix when FreeType looks for it.
#
# libjxl vendors brotli as well, and its install writes libbrotlidec.a and libbrotlicommon.a over
# the ones built here. That is harmless and is left alone: it is the same upstream library, the
# two FreeType links come from libjxl's tree as a matched pair, and only the decoder is ever linked
# -- libbrotlienc.a is the one file of this project's own build that survives, and nothing asks for
# it. Telling libjxl to use the copy already installed (JPEGXL_FORCE_SYSTEM_BROTLI) does work, but
# SDL3_image then tries to install brotli targets that no longer exist, and patching its install
# list is a worse trade than one duplicated build.
singeCmakeProject(brotli ${SB_THIRDPARTY}/brotli "" "-DBUILD_SHARED_LIBS=off;-DBROTLI_BUILD_TOOLS=off;-DBROTLI_DISABLE_TESTS=on" "${SB_ENV}")
singeCmakeProject(SDL3_image ${SB_THIRDPARTY}/SDL3_image "SDL3;zlib;brotli"
"-DBUILD_SHARED_LIBS=off;-DSDLIMAGE_DEPS_SHARED=off;-DSDLIMAGE_SAMPLES=off;-DSDLIMAGE_TESTS=off;-DSDLIMAGE_VENDORED=on;-DSDLIMAGE_BACKEND_STB=off;-DSDLIMAGE_AVIF=off;-DSDLIMAGE_JXL=on;-DSDLIMAGE_JXL_SHARED=off;-DBUILD_TESTING=OFF;-DSDLIMAGE_TIF=off;-DSDLIMAGE_WEBP=on;${sdl3Dir};-DWEBP_BUILD_ANIM_UTILS=off;-DWEBP_BUILD_CWEBP=off;-DWEBP_BUILD_DWEBP=off;-DWEBP_BUILD_GIF2WEBP=off;-DWEBP_BUILD_IMG2WEBP=off;-DWEBP_BUILD_VWEBP=off;-DWEBP_BUILD_WEBPINFO=off;-DWEBP_BUILD_WEBPMUX=off;-DWEBP_BUILD_EXTRAS=off"
"${SB_ENV_NO_PREFIX_INCLUDE}")
# SDL3_image builds libpng and installs the archive but not its headers, and FreeType needs both
# before it will read a font's colour bitmap glyphs. pnglibconf.h is generated, so it comes from
# the build tree rather than the source one.
ExternalProject_Add_Step(SDL3_image pngHeaders
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SB_THIRDPARTY}/SDL3_image/external/libpng/png.h ${SB_THIRDPARTY}/SDL3_image/external/libpng/pngconf.h ${SB_PREFIX}/build/SDL3_image/external/libpng-build/pnglibconf.h ${SB_PREFIX}/include
DEPENDEES install
COMMENT "Installing the libpng headers"
)
singeCmakeProject(SDL3_mixer ${SB_THIRDPARTY}/SDL3_mixer "SDL3"
"-DBUILD_SHARED_LIBS=off;-DSDLMIXER_DEPS_SHARED=off;-DSDLMIXER_VENDORED=on;-DSDLMIXER_EXAMPLES=off;-DSDLMIXER_TESTS=off;-DSDLMIXER_FLAC_LIBFLAC=off;-DSDLMIXER_MP3_MPG123=off;-DSDLMIXER_VORBIS_STB=on;-DSDLMIXER_VORBIS_VORBISFILE=off;-DSDLMIXER_VORBIS_TREMOR=off;-DSDLMIXER_GME=on;-DSDLMIXER_GME_SHARED=off;-DGME_BUILD_EXAMPLES=off;-DGME_BUILD_TESTING=off;-DGME_YM2612_EMU=MAME;-DSDLMIXER_MIDI_FLUIDSYNTH=off;${sdl3Dir};-DWAVPACK_ENABLE_ASM=no"
"${SB_ENV}")
singeCmakeProject(SDL3_ttf ${SB_THIRDPARTY}/SDL3_ttf "SDL3;zlib;brotli;SDL3_image"
"-DBUILD_SHARED_LIBS=off;-DSDLTTF_VENDORED=on;-DSDLTTF_HARFBUZZ=on;-DSDLTTF_PLUTOSVG=on;-DHB_HAVE_CORETEXT=off;-DSDLTTF_SAMPLES=off;-DFT_DISABLE_ZLIB=off;-DFT_DISABLE_BROTLI=off;-DZLIB_INCLUDE_DIR=${SB_PREFIX}/include;-DZLIB_LIBRARY=${SB_PREFIX}/lib/libz.a;-DBROTLIDEC_INCLUDE_DIRS=${SB_PREFIX}/include;-DBROTLIDEC_LIBRARIES=${SB_PREFIX}/lib/libbrotlidec.a;-DFT_DISABLE_PNG=off;-DPNG_PNG_INCLUDE_DIR=${SB_PREFIX}/include;-DPNG_LIBRARY=${SB_PREFIX}/lib/libpng16.a;${sdl3Dir}"
"${SB_ENV}")
# SDL3_ttf builds its vendored FreeType but does not install it, and Singe links it directly.
ExternalProject_Add_Step(SDL3_ttf freetype
COMMAND ${CMAKE_COMMAND} -E copy ${SB_PREFIX}/build/SDL3_ttf/external/freetype/libfreetype.a ${SB_PREFIX}/lib/libfreetype.a
DEPENDEES install
COMMENT "Installing libfreetype.a"
)
# ===== RmlUi (C++; static, the core, the Lua plugin and the debugger; no samples) =====
# FreeType is the copy SDL3_ttf builds (installed as libfreetype.a below, headers from its tree);
# Lua is the engine's own, handed over as a header-only target by cmake/rmluiLua.cmake.
set(rmluiOptions
-DBUILD_SHARED_LIBS=OFF -DRMLUI_SAMPLES=OFF -DRMLUI_PRECOMPILED_HEADERS=OFF
-DRMLUI_FONT_ENGINE=freetype -DRMLUI_LUA_BINDINGS=ON -DRMLUI_LUA_BINDINGS_LIBRARY=lua
-DFREETYPE_LIBRARY=${SB_PREFIX}/lib/libfreetype.a
-DFREETYPE_INCLUDE_DIR_ft2build=${SB_THIRDPARTY}/SDL3_ttf/external/freetype/include
-DFREETYPE_INCLUDE_DIR_freetype2=${SB_THIRDPARTY}/SDL3_ttf/external/freetype/include
-DSINGE_LUA_INCLUDE=${SB_THIRDPARTY}/lua/src
-DCMAKE_PROJECT_RmlUi_INCLUDE=${CMAKE_CURRENT_LIST_DIR}/rmluiLua.cmake
)
singeCmakeProject(RmlUi ${SB_THIRDPARTY}/RmlUi "SDL3_ttf" "${rmluiOptions}" "${SB_ENV}")
# ===== OpenSSL (its own Configure; the target name follows the platform) =====
if(KANGAROO_OS STREQUAL "windows")
if(KANGAROO_ARCH STREQUAL "x86")
set(opensslTarget mingw)
else()
set(opensslTarget mingw64)
endif()
elseif(KANGAROO_OS STREQUAL "linux")
if(KANGAROO_ARCH STREQUAL "aarch64")
set(opensslTarget linux-aarch64)
elseif(KANGAROO_ARCH STREQUAL "armhf")
set(opensslTarget linux-armv4)
elseif(SINGE_ZIG_TARGET)
set(opensslTarget linux-x86_64)
else()
set(opensslTarget "")
endif()
elseif(KANGAROO_OS STREQUAL "macos")
if(KANGAROO_ARCH STREQUAL "aarch64")
set(opensslTarget darwin64-arm64)
else()
set(opensslTarget darwin64-x86_64)
endif()
elseif(SINGE_ZIG_TARGET)
set(opensslTarget linux-x86_64)
else()
set(opensslTarget "")
endif()
# OpenSSL's generated assembly uses GNU as directives clang's assembler rejects; under zig the
# portable C paths serve, which is all LuaSec's traffic needs.
if(SINGE_ZIG_TARGET)
set(opensslAsm no-asm)
else()
set(opensslAsm "")
endif()
# OpenSSL's Configure derives the source tree from the build directory's position and only
# copes with a build directory nested inside its own tree, so this one lives there, one per target
# so that switching presets never links another architecture's objects.
set(opensslBinary ${SB_THIRDPARTY}/openssl/.builddir/${KANGAROO_OS}-${KANGAROO_ARCH})
ExternalProject_Add(openssl
SOURCE_DIR ${SB_THIRDPARTY}/openssl
BINARY_DIR ${opensslBinary}
DEPENDS zlib zstd
CONFIGURE_COMMAND ${SB_ENV} perl ../../Configure ${opensslTarget} --prefix=${SB_PREFIX} --libdir=lib --with-zlib-include=${SB_PREFIX}/include --with-zlib-lib=${SB_PREFIX}/lib --with-zstd-include=${SB_PREFIX}/include --with-zstd-lib=${SB_PREFIX}/lib ${opensslAsm} no-apps no-docs no-dso no-dynamic-engine no-module no-shared no-tests zlib enable-zstd
BUILD_COMMAND ${SB_ENV} make build_sw
INSTALL_COMMAND ${SB_ENV} make install_sw
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON
)
singeRebuildTarget(openssl)
# ===== FFmpeg (hardware decoding per platform; CMakeLists.txt reads its .pc files back) =====
# ===== Rockchip Media Process Platform (the vendor decode library for Rockchip SoCs) =====
# Rockchip's decoders are aarch64 only. Because the library links statically it costs nothing on a
# board that is not a Rockchip, so it is built for the aarch64 targets rather than needing one of
# its own; set SINGE_ROCKCHIP_MPP to override either way.
if(NOT DEFINED SINGE_ROCKCHIP_MPP)
if((KANGAROO_OS STREQUAL "linux") AND (KANGAROO_ARCH STREQUAL "aarch64"))
set(SINGE_ROCKCHIP_MPP ON)
else()
set(SINGE_ROCKCHIP_MPP OFF)
endif()
endif()
# MPP is packaged by no distribution, so it is vendored and built here like everything else. Only
# the static library is kept: linked statically the Singe binary carries no runtime dependency on
# it, so one ARM build runs on a Rockchip board and on anything else, where the decoder simply fails
# to open a device and the player falls back. A shared build would put librockchip_mpp.so in the
# binary's DT_NEEDED and stop it starting anywhere the library is absent.
set(rkmppFlag "")
set(rkmppDep "")
if(SINGE_ROCKCHIP_MPP)
# MPP strips its merged archive with ${CMAKE_STRIP}, and no cross strip here understands an ar
# archive: zig's objcopy is ELF only. The step only drops debug symbols from an intermediate
# library, so it is skipped; the release binary is stripped at its own link.
singeCmakeProject(rockchipMpp ${SB_THIRDPARTY}/rockchip-mpp "" "-DBUILD_SHARED_LIBS=OFF;-DBUILD_TEST=OFF;-DHAVE_DRM=ON;-DCMAKE_STRIP=/bin/true" "${SB_ENV}")
# Its install puts both the archive and the shared object down; the shared one is removed so
# that -lrockchip_mpp can only resolve to the archive.
ExternalProject_Add_Step(rockchipMpp staticOnly
COMMAND ${CMAKE_COMMAND} -E rm -f ${SB_PREFIX}/lib/librockchip_mpp.so ${SB_PREFIX}/lib/librockchip_mpp.so.0 ${SB_PREFIX}/lib/librockchip_mpp.so.1 ${SB_PREFIX}/lib/librockchip_vpu.so ${SB_PREFIX}/lib/librockchip_vpu.so.0 ${SB_PREFIX}/lib/librockchip_vpu.so.1
DEPENDEES install
COMMENT "Keeping only the static Rockchip MPP library"
)
set(rkmppFlag --enable-rkmpp)
set(rkmppDep rockchipMpp)
message(STATUS "Rockchip MPP will be built and linked statically; the rkmpp decoders are enabled")
endif()
# ===== dav1d, the AV1 decoder =====
# FFmpeg's own AV1 decoder needs hardware behind it: with no hwaccel it says "Your platform doesn't
# support hardware accelerated AV1 decoding" and decodes nothing, so an AV1 video does not play at
# all on a machine without an AV1 capable GPU. dav1d is the software decoder, and it brings AVIF
# stills with it, an AVIF being an AV1 picture in a HEIF box. It is the one vendored library whose
# own build system is Meson rather than CMake or a configure script.
set(dav1dBinary ${SB_PREFIX}/build/dav1d)
set(dav1dCross "")
if(SINGE_ZIG_TARGET)
# Meson learns a cross build from a file rather than from the environment, and names both the
# system and some architectures its own way. The system name is not cosmetic: Meson picks
# nasm's output format from it, and calling Windows "mingw32" -- which is what FFmpeg calls it
# -- leaves nasm emitting ELF objects that the Windows linker will not read.
if(KANGAROO_OS STREQUAL "windows")
set(dav1dSystem windows)
elseif(KANGAROO_OS STREQUAL "macos")
set(dav1dSystem darwin)
else()
set(dav1dSystem linux)
endif()
if(KANGAROO_ARCH STREQUAL "armhf")
set(dav1dCpuFamily arm)
else()
set(dav1dCpuFamily ${KANGAROO_ARCH})
endif()
set(dav1dCrossFile ${SB_PREFIX}/build/dav1dCross.txt)
file(WRITE ${dav1dCrossFile}
"[binaries]\nc = '${SB_CC}'\ncpp = '${SB_CXX}'\nar = '${CMAKE_AR}'\nranlib = '${SB_RANLIB}'\nstrip = 'true'\npkg-config = 'pkg-config'\nnasm = 'nasm'\n\n[host_machine]\nsystem = '${dav1dSystem}'\ncpu_family = '${dav1dCpuFamily}'\ncpu = '${KANGAROO_ARCH}'\nendian = 'little'\n")
set(dav1dCross --cross-file ${dav1dCrossFile})
endif()
ExternalProject_Add(dav1d
SOURCE_DIR ${SB_THIRDPARTY}/dav1d
BINARY_DIR ${dav1dBinary}
CONFIGURE_COMMAND ${SB_ENV} meson setup --wipe --prefix=${SB_PREFIX} --libdir=lib --buildtype=release --default-library=static -Denable_tools=false -Denable_tests=false ${dav1dCross} ${dav1dBinary} ${SB_THIRDPARTY}/dav1d
BUILD_COMMAND ${SB_ENV} ninja -C ${dav1dBinary}
INSTALL_COMMAND ${SB_ENV} ninja -C ${dav1dBinary} install
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON
)
singeRebuildTarget(dav1d)
if(KANGAROO_OS STREQUAL "linux")
# ARM boards decode through a V4L2 memory-to-memory device; desktops have VAAPI or VDPAU.
if(KANGAROO_ARCH MATCHES "^(aarch64|armhf)$")
set(hwaccel --enable-v4l2-m2m --enable-libdrm ${rkmppFlag})
else()
set(hwaccel --enable-vaapi --enable-vdpau --enable-libdrm ${rkmppFlag})
endif()
elseif(KANGAROO_OS STREQUAL "macos")
set(hwaccel --enable-videotoolbox)
elseif(KANGAROO_OS STREQUAL "windows")
set(hwaccel --enable-d3d11va --enable-dxva2)
else()
set(hwaccel "")
endif()
# With a classic cross toolchain every tool is <triple>-<tool>; with zig each is named outright.
if(SINGE_ZIG_TARGET)
set(ffmpegTools --enable-cross-compile --cc=${SB_CC} --cxx=${SB_CXX} --ar=${CMAKE_AR} --ranlib=${SB_RANLIB} --windres=${CMAKE_RC_COMPILER})
# FFmpeg only uses nm to learn the symbol prefix. The host's binutils nm reads x86 ELF and PE
# objects but not aarch64 or Mach-O ones; the prefix is empty on Linux either way, and on macOS
# (where it is an underscore) llvm-nm from Debian's llvm package reads Mach-O.
if(KANGAROO_OS STREQUAL "macos")
find_program(SINGE_LLVM_NM NAMES llvm-nm llvm-nm-21 llvm-nm-20 llvm-nm-19 llvm-nm-18 llvm-nm-17 llvm-nm-16 llvm-nm-15)
if(NOT SINGE_LLVM_NM)
message(FATAL_ERROR "llvm-nm is needed for the macOS FFmpeg build (apt-get install llvm).")
endif()
list(APPEND ffmpegTools --nm=${SINGE_LLVM_NM})
elseif(KANGAROO_ARCH STREQUAL "x86_64")
list(APPEND ffmpegTools --nm=nm)
endif()
else()
set(ffmpegTools --cross-prefix=${SINGE_TRIPLE}- --cc=${SB_CC} --cxx=${SB_CXX} --ranlib=${SB_RANLIB})
endif()
# FFmpeg's configure sees sys/sysctl.h in the Pi sysroot and in zig's glibc header set, then its
# build cannot include it; an empty stand-in satisfies both (Linux uses sysconf, not sysctl).
if((KANGAROO_OS STREQUAL "linux") AND SINGE_ZIG_TARGET)
file(WRITE ${SB_PREFIX}/include/sys/sysctl.h "/* File no longer used */\n")
endif()
set(ffmpegBinary ${SB_PREFIX}/build/ffmpeg)
ExternalProject_Add(ffmpeg
SOURCE_DIR ${SB_THIRDPARTY}/ffmpeg
BINARY_DIR ${ffmpegBinary}
DEPENDS dav1d ${rkmppDep}
CONFIGURE_COMMAND ${SB_ENV} ${SB_THIRDPARTY}/ffmpeg/configure --enable-static --disable-shared --disable-debug --disable-muxers ${hwaccel} --disable-encoders --disable-filters --enable-filter=bwdif --enable-filter=yadif --enable-filter=buffer --enable-filter=buffersink --enable-filter=format --disable-network --disable-devices --disable-vulkan --disable-d3d12va --disable-bzlib --disable-lzma --disable-doc --disable-programs --enable-libdav1d --enable-gpl --enable-version3 --extra-ldflags=-L${SB_PREFIX}/lib --prefix=${SB_PREFIX} --arch=${KANGAROO_ARCH} --target-os=${SINGE_CROSS_OS} ${ffmpegTools}
BUILD_COMMAND ${SB_ENV} make
INSTALL_COMMAND ${SB_ENV} make install
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON
)
singeRebuildTarget(ffmpeg)
# ===== Singe itself, against everything above =====
string(SUBSTRING ${KANGAROO_OS} 0 1 osInitial)
string(SUBSTRING ${KANGAROO_OS} 1 -1 osRest)
string(TOUPPER ${osInitial} osInitial)
# The built file carries the platform's executable suffix; the copy in .builddir/ carries the
# preset's, which is the same thing for Windows and lets an experimental build live beside another.
set(singeBinaryBase "Singe-v${PROJECT_VERSION}-${osInitial}${osRest}-${KANGAROO_ARCH}")
set(singeBinaryName "${singeBinaryBase}${SINGE_SUFFIX}")
set(singeBinary ${SB_PREFIX}/singe)
ExternalProject_Add(singe
SOURCE_DIR ${CMAKE_SOURCE_DIR}
BINARY_DIR ${singeBinary}
DEPENDS zlib zstd SDL3 SDL3_image SDL3_mixer SDL3_ttf openssl ffmpeg Jolt recastnavigation RmlUi shadercross
CONFIGURE_COMMAND ${SB_ENV} ${CMAKE_COMMAND} -S ${CMAKE_SOURCE_DIR} -B ${singeBinary} ${SB_CMAKE_ARGS} -DSINGE_SHADERCROSS=${SINGE_SHADERCROSS} -DSINGE_SUPERBUILD=OFF -DKANGAROO_OS=${KANGAROO_OS} -DKANGAROO_ARCH=${KANGAROO_ARCH} -DSINGE_TREE=${SINGE_TREE}
BUILD_COMMAND ${SB_ENV} ${CMAKE_COMMAND} --build ${singeBinary} --parallel ${SB_JOBS}
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${singeBinary}/${singeBinaryBase}${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_SOURCE_DIR}/.builddir/${singeBinaryName}
BUILD_ALWAYS ON
)
# Forge, packed into .builddir beside the binary so it can be copied out and tested. It is NOT
# part of the engine and nothing of it is embedded; this only puts the distributable where the
# binaries already are. Only a build that produces a runnable binary can pack it, since packing is
# the engine's own --pack, so cross builds skip it and the host build does the work. The manual,
# rendered by the engine's own build into .builddir, goes in beside the scripts: Forge copies it
# out to its data directory on its first run.
if((KANGAROO_OS STREQUAL "linux") AND (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux"))
file(GLOB forgeSources ${CMAKE_SOURCE_DIR}/assets/Forge/*)
set(forgePack ${CMAKE_SOURCE_DIR}/.builddir/Forge.game)
set(forgeManual ${CMAKE_SOURCE_DIR}/.builddir/Forge.pdf)
set(forgeStage ${SB_PREFIX}/forgepack)
add_custom_command(
OUTPUT ${forgePack}
# Packing refuses to overwrite, and a stale pack is worse than none.
COMMAND ${CMAKE_COMMAND} -E rm -rf ${forgePack} ${forgeStage}
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/Forge ${forgeStage}/Forge
COMMAND ${CMAKE_COMMAND} -E copy ${forgeManual} ${forgeStage}/Forge/Forge.pdf
# Run it somewhere harmless: the engine writes Singe/ and data/ into the working directory.
COMMAND ${CMAKE_COMMAND} -E chdir ${forgeStage} ${CMAKE_SOURCE_DIR}/.builddir/${singeBinaryName} --pack ${forgeStage}/Forge ${forgePack}
DEPENDS singe ${forgeSources} ${forgeManual}
COMMENT "Packing Forge into .builddir/Forge.game"
VERBATIM
)
add_custom_target(forge ALL DEPENDS ${forgePack})
endif()
message(STATUS "Superbuild for ${KANGAROO_OS}/${KANGAROO_ARCH}: libraries into ${SB_PREFIX}, binary ${singeBinaryName}")