Files
source/CMakeLists.txt
T
Marco Antonio J. Costaandmajcosta 5fd1d5f567 build with clang-cl from a toolchain file and a preset
The tree compiles and links under clang-cl, so make that a configuration
anyone can ask for rather than a command line to reconstruct:

  MSVC_DIRECTORY=/path/to/msvc cmake --preset '1dot13 Release clang-cl'

It is a check on the source, not a port -- MSVC still builds what ships. What
it buys is a second front end over every translation unit, which is how the
inline assembly with no operand size and the resource string with a raw 0xA9
were found, and it runs natively: clang-cl, lld-link, llvm-rc and llvm-lib
need no Wine, so a full JA2.exe takes a fraction of the time.

The toolchain file names every MSVC and SDK include directory, because
clang-cl does not read INCLUDE and LIB the way cl does, and picks the newest
version of each tree rather than pinning one. MSVC_DIRECTORY says where they
live and is reported plainly when it is missing or wrong.

build a bindable Bink import library when not building with MSVC

The shipped binkw32.lib is an old ordinal-based long-format import library.
MSVC's link.exe binds it, but clang-cl's lld-link does not: it leaves every
Bink import unresolved, so at run time the calls jump into arbitrary code and
crash the game at startup, six access violations deep in unrelated code before
the game's own handler gives up.

Rebuild the import library from binkw32.def with llvm-dlltool on any front end
other than MSVC. The library imports by ordinal, for the reasons binkw32.def
records: binkw32.dll exports decorated stdcall names that keep their leading
underscore (_BinkOpen@8), and llvm-dlltool strips that underscore from the
imported name whatever --no-leading-underscore says, so a by-name import would
look up a name the DLL does not export and Wine would substitute a builtin stub
that aborts ("unimplemented function binkw32.dll.BinkSetSoundSystem@8").
Ordinals have no such ambiguity and match the retail DLL. MSVC keeps binding the
shipped library, so the retail build is untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

refactor toolchain

make the preset template something CMake will read

The template carries two // comments. JSON has no comments and neither does
the preset format, so CMake refuses the whole file:

  CMakePresets.json:10: Missing '}' or object member name

Which means the file CopyUserPresetTemplate drops into the source directory
has never worked as handed over -- "cmake --preset '1dot13 Release'" fails
until you delete the comments yourself.

The text they carried is worth keeping, so it moves to the description field
the format does have, and cmake --preset '1dot13 Release' now configures.
2026-07-23 19:29:55 -03:00

186 lines
6.2 KiB
CMake

cmake_minimum_required(VERSION 3.20)
project(ja2)
include(cmake/CopyUserPresetTemplate.cmake)
CopyUserPresetTemplate()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
option(LTO_OPTION "Enable link-time optimization if supported by compiler" OFF)
include(CheckIPOSupported)
check_ipo_supported(RESULT LinkTimeOptimization OUTPUT IpoError LANGUAGES C CXX)
if(LinkTimeOptimization AND LTO_OPTION)
message(STATUS "Configuring WITH link-time optimization")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
else()
message(STATUS "Configuring WITHOUT link-time optimization ${IpoError}")
endif()
option(ADDRESS_SANITIZER OFF)
if(ADDRESS_SANITIZER)
message(STATUS "AddressSanitizer ENABLED for non-Release builds")
add_compile_options($<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,-fsanitize=address,>)
endif()
if(MSVC)
# TODO: fix the warnings here and turn them on
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warnings-c4000-through-c4199
add_compile_options("/wd4838") # silence implicit narrowing conversion warnings
# add_compile_options("/w14061")
# add_compile_options("/w14062")
endif()
# whether we are using MSBuild as a generator
set(usingMsBuild $<STREQUAL:${CMAKE_VS_PLATFORM_NAME},Win32>)
# lua51.lib and lua51.vc9.lib have been built with /MTx, so we must as well
# TODO: build our own Lua 5.1.2 from source so we can use whichever
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
add_compile_definitions(CINTERFACE XML_STATIC VFS_STATIC VFS_WITH_SLF VFS_WITH_7ZIP _CRT_SECURE_NO_DEPRECATE)
include_directories(
"${CMAKE_SOURCE_DIR}/Ja2"
"${CMAKE_SOURCE_DIR}/ext/VFS/include"
"${CMAKE_SOURCE_DIR}/ext/libsmacker"
"${CMAKE_SOURCE_DIR}/Utils"
"${CMAKE_SOURCE_DIR}/TileEngine"
"${CMAKE_SOURCE_DIR}/TacticalAI"
"${CMAKE_SOURCE_DIR}/ModularizedTacticalAI/include"
"${CMAKE_SOURCE_DIR}/Tactical"
"${CMAKE_SOURCE_DIR}/Strategic"
"${CMAKE_SOURCE_DIR}/sgp"
"${CMAKE_SOURCE_DIR}/Ja2/Res"
"${CMAKE_SOURCE_DIR}/lua"
"${CMAKE_SOURCE_DIR}/Laptop"
"${CMAKE_SOURCE_DIR}/Multiplayer"
"${CMAKE_SOURCE_DIR}/Editor"
"${CMAKE_SOURCE_DIR}/i18n/include"
)
# external libraries
add_subdirectory("ext/libpng")
add_subdirectory("ext/libsmacker")
add_subdirectory("ext/zlib")
add_subdirectory("ext/VFS")
target_link_libraries(bfVFS PRIVATE 7z)
# ja2export utility
add_subdirectory("ext/export/src")
# static libraries whose translation units don't rely on Application preprocessor definitions.
add_subdirectory(lua)
add_subdirectory(Multiplayer)
add_subdirectory(wine)
# A non-MSVC toolchain rebuilds the Bink import library and points
# binkw32_lib at it (see clang-cl.cmake); MSVC binds the shipped
# library fine, so fall back to that.
if(NOT binkw32_lib)
set(binkw32_lib "${CMAKE_SOURCE_DIR}/binkw32.lib")
endif()
set(Ja2_Libraries
"${binkw32_lib}"
"${CMAKE_SOURCE_DIR}/libexpatMT.lib"
"${CMAKE_SOURCE_DIR}/lua51.lib"
"${CMAKE_SOURCE_DIR}/lua51.vc9.lib"
"dbghelp.lib"
"winmm.lib"
"ws2_32.lib"
bfVFS
Lua
Multiplayer
smacker
wine
)
# static libraries whose translation units rely on Application preprocessor definitions.
set(Ja2_Libs
Editor
Ja2
Laptop
ModularizedTacticalAI
sgp
Strategic
Tactical
TacticalAI
TileEngine
Utils
)
foreach(lib IN LISTS Ja2_Libs)
add_subdirectory(${lib})
endforeach()
# language library relies on Application preprocessor definition; compiled once per app
# with all 8 languages built in and selected at runtime (docs/plans/language-design.md).
add_subdirectory(i18n)
# simple function to validate the Application choice
include(cmake/ValidateOptions.cmake)
set(ValidApplications JA2 JA2MAPEDITOR JA2UB JA2UBMAPEDITOR)
ValidateOptions("${ValidApplications}" "Applications" "${Applications}" "ApplicationTargets")
# preprocessor definitions for Debug build, per the legacy MSBuild
set(debugFlags $<IF:$<CONFIG:Debug>,JA2BETAVERSION;JA2TESTVERSION;DEBUG_ATTACKBUSY,>)
foreach(app IN LISTS ApplicationTargets)
set(isEditor $<STREQUAL:${app},JA2MAPEDITOR>)
set(isUb $<STREQUAL:${app},JA2UB>)
set(isUbEditor $<STREQUAL:${app},JA2UBMAPEDITOR>)
set(compilationFlags
$<IF:${isEditor},JA2EDITOR;JA2BETAVERSION,>
$<IF:${isUb},JA2UB;JA2UBMAPS,>
$<IF:${isUbEditor},JA2UB;JA2UBMAPS;JA2EDITOR;JA2BETAVERSION,>
)
foreach(lib IN LISTS Ja2_Libs)
# library for an application, e.g. JA2UB_sgp
set(game_library ${app}_${lib})
add_library(${game_library})
target_sources(${game_library} PRIVATE ${${lib}Src})
target_compile_definitions(${game_library} PRIVATE ${compilationFlags} ${debugFlags})
endforeach()
# executable for the application, e.g. JA2.exe
set(exe ${app})
add_executable(${exe} WIN32
sgp/sgp.cpp
Ja2/Res/ja2.rc
)
# RakNetLibStatic.lib was built against an older CRT and still calls
# _vsnprintf, which the UCRT only exports through this compatibility library.
target_link_libraries(${exe} PRIVATE ${Ja2_Libraries} legacy_stdio_definitions.lib)
# The prebuilt lua51.lib and smackw32 import library carry no safe exception
# handler table, so the image cannot claim one whatever links it.
target_link_options(${exe} PRIVATE /SAFESEH:NO)
target_compile_definitions(${exe} PRIVATE ${compilationFlags} ${debugFlags})
# language library for the application, e.g. JA2MAPEDITOR_i18n — one per app, all 8
# languages compiled in, selected at runtime by BindLanguageStrings
set(language_library ${exe}_i18n)
add_library(${language_library})
target_sources(${language_library} PRIVATE ${i18nSrc})
target_compile_definitions(${language_library} PRIVATE ${compilationFlags} ${debugFlags})
target_compile_options(${language_library} PRIVATE /w14062)
target_link_libraries(${exe} PRIVATE ${language_library})
# go through all game libraries again and link them to the app executable
foreach(lib IN LISTS Ja2_Libs)
target_link_libraries(${exe} PRIVATE ${app}_${lib})
endforeach()
# for Utils only
target_link_libraries(${app}_Utils PRIVATE smacker)
# for SGP only
target_link_libraries(${app}_sgp PRIVATE "ddraw.lib" "${PROJECT_SOURCE_DIR}/fmodvc.lib")
target_link_libraries(${app}_sgp PRIVATE libpng)
target_compile_definitions(${app}_sgp PRIVATE ${compilationFlags} ${debugFlags} NO_ZLIB_COMPRESSION)
endforeach()