Cockatrice/CMakeLists.txt
BruebachL 6385f3fab3
[CrashReporting] Mock crash-report dialog and reporting preference (#7521)
* [CrashReporting] Mock dialog, sentry skeleton and CI prerequisites

Mock: DlgCrashReport preview dialog (no account needed, redacted log
preview), CrashReportingMode Ask/Always/Never setting in DebugSettings,
--simulate-crash flag and Help-menu preview entry. Send writes a local
mock JSON only.

Vendor sentry-native 0.17.3 via FetchContent behind
WITH_CRASH_REPORTING (breakpad everywhere, backend none under
sanitizers) plus a CrashReporter facade with early init and post-loop
shutdown. DSN is a placeholder: empty CMake default with
COCKATRICE_SENTRY_DSN env override, so SDK init is skipped and mock
behavior is unchanged until a real DSN is configured.

CI prerequisites riding here so every layer below builds green:
libcurl dev plus CA bundles in the Linux images and CodeQL env, sentry
Qt integration off (its own find_package cannot see the macOS thin Qt
layout), and the StrFunc Using construction makensis requires.

Tests: ctest green, format clean, code plus UI gates PASS.

* [CrashReporting] Forward mode changes to SDK and fix Linux CI opt-in

Connects PersonalSettings::crashReportingModeChanged to
giveConsent()/revokeConsent() so switching from AlwaysSend to
NeverSend (or Ask) mid-session reaches the SDK instead of leaving
consent given until restart.

Forwards WITH_CRASH_REPORTING into the docker container the way
CCACHE_DIR is, so the Linux package leg actually builds with crash
reporting ON instead of silently building OFF.

* [CrashReporting] Give crash-report logging its own category per review

* [CrashReporting] Drop unused DebugSettings constructor visibility per review

* [CrashReporting] Document reporting modes on the enum per review

* [CrashReporting] Match the dump scan to the writer's temp fallback

Review nit: when the handler's SHGetKnownFolderPath lookup fails it writes to
<temp>/../.., but the startup scan looked in QDir::tempPath(), so a dump
produced through the fallback would never be found and its crash would never
offer to be reported. Walk up the same two levels in the scan.

The path is Windows-only (#ifdef Q_OS_WIN); the statements were compiled and
run standalone against Qt 6 to confirm the API use.

* [CrashReporting] Complete the dump-scan fallback path

The previous commit walked the scan's fallback up two levels but dropped the
CrashDumps/Cockatrice suffix that the writer appends unconditionally, so a dump
written through the fallback was still invisible to the scan.

Evaluate the writer's expression verbatim rather than mirroring it with QDir:
cdUp() is existence-checked and silently leaves the path alone when the
directory is missing, parent_path() is not, and QDir::absoluteFilePath() joins
the shared suffix without risking a doubled separator when the walk lands on a
root. Verified by compiling the block verbatim against Qt 6 with -Wextra
-Werror and comparing it with the writer's join for both the normal and the
fallback case.

Also record that %LOCALAPPDATA% is the scan's stand-in for
FOLDERID_LocalAppData and why the two normally agree.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-19 14:57:03 +02:00

438 lines
17 KiB
CMake

# Cockatrice's main CMakeLists.txt
#
# This is basically a wrapper to enable/disable the compilation
# of the different projects: servatrice, cockatrice, test
# This file sets all the variables shared between the projects
# like the installation path, compilation flags etc..
# 3.16 required for Qt6 and target_precompile_headers()
cmake_minimum_required(VERSION 3.16)
# Use compiler cache (ccache)
option(USE_CCACHE "Cache the build results with ccache" ON)
# Treat warnings as errors (Debug builds only)
option(WARNING_AS_ERROR "Treat warnings as errors in debug builds" ON)
# Check for translation updates
option(UPDATE_TRANSLATIONS "Update translations on compile" OFF)
# Compile Cockatrice
option(WITH_CLIENT "Build Cockatrice client" ON)
# Compile Oracle
option(WITH_ORACLE "Build Cockatrice card database tool (Oracle)" ON)
# Compile Servatrice
option(WITH_SERVER "Build Cockatrice server (Servatrice)" OFF)
# Compile tests
option(TEST "Build tests" OFF)
# Build with AddressSanitizer and UndefinedBehaviorSanitizer
option(ENABLE_SANITIZERS "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF)
# Build instrumented for GCC line coverage, reported with gcovr
option(ENABLE_COVERAGE "Build instrumented for GCC line coverage" OFF)
# Use vcpkg regardless of OS
option(USE_VCPKG "Use vcpkg regardless of OS" OFF)
# Default to "Release" build type
# User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call
if(DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE
${CMAKE_BUILD_TYPE}
CACHE STRING "Type of build"
)
else()
set(CMAKE_BUILD_TYPE
Release
CACHE STRING "Type of build"
)
endif()
# ccache does not support MSVC and must not auto-engage on Windows
# (it is installed unintentionally on the Windows CI runner).
# NOTE: this keys off the target OS, so a mingw/Ninja configuration on Windows
# also opts out of ccache even though the GNUCXX branch below supports it.
if(USE_CCACHE AND NOT WIN32)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
# Support Unix Makefiles and Ninja
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
# PCH-aware caching, matching .ci/compile.sh: without this ccache refuses
# to cache any TU that consumes a precompiled header, so every PCH-backed
# target recompiles from scratch on each build.
execute_process(COMMAND ${CCACHE_PROGRAM} --set-config sloppiness=pch_defines,time_macros)
message(STATUS "Found CCache ${CCACHE_PROGRAM}")
endif()
elseif(USE_CCACHE AND WIN32)
# An explicit opt-in must not disappear silently on Windows.
message(STATUS "ccache disabled: not supported for the MSVC toolchain on Windows")
endif()
if(WIN32 OR USE_VCPKG)
# Use vcpkg toolchain on Windows (and on macOS in CI)
set(CMAKE_TOOLCHAIN_FILE
${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake
CACHE STRING "Vcpkg toolchain file"
)
# Qt path set by user or env var
if(QTDIR
OR DEFINED ENV{QTDIR}
OR DEFINED ENV{QTDIR32}
OR DEFINED ENV{QTDIR64}
)
else()
set(QTDIR
""
CACHE PATH "Path to Qt (e.g. C:/Qt/6.4.2/msvc2019_64)"
)
message(
WARNING
"QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/6.4.2/msvc2019_64)"
)
endif()
endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
project("Cockatrice" VERSION 3.1.2)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Reality Check")
endif()
# Use c++20 for all targets
set(CMAKE_CXX_STANDARD
20
CACHE STRING "C++ ISO Standard"
)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# Set conventional loops
set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true)
# Search path for cmake modules
set(COCKATRICE_CMAKE_PATH "${PROJECT_SOURCE_DIR}/cmake")
list(INSERT CMAKE_MODULE_PATH 0 "${COCKATRICE_CMAKE_PATH}")
include(getversion)
# Create a header and a cpp file containing the version hash
include(createversionfile)
# Define a proper install path
if(UNIX)
if(APPLE)
# macOS
# Due to the special bundle structure ignore
# the prefix eventually set by the user.
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/release)
# Force ccache usage if available
get_property(RULE_LAUNCH_COMPILE GLOBAL PROPERTY RULE_LAUNCH_COMPILE)
if(RULE_LAUNCH_COMPILE)
message(STATUS "Force enabling CCache usage under macOS")
# Set up wrapper scripts
configure_file("${COCKATRICE_CMAKE_PATH}/launch-c.in" launch-c)
configure_file("${COCKATRICE_CMAKE_PATH}/launch-cxx.in" launch-cxx)
execute_process(COMMAND chmod a+rx "${CMAKE_BINARY_DIR}/launch-c" "${CMAKE_BINARY_DIR}/launch-cxx")
# Set Xcode project attributes to route compilation through our scripts
set(CMAKE_XCODE_ATTRIBUTE_CC "${CMAKE_BINARY_DIR}/launch-c")
set(CMAKE_XCODE_ATTRIBUTE_CXX "${CMAKE_BINARY_DIR}/launch-cxx")
set(CMAKE_XCODE_ATTRIBUTE_LD "${CMAKE_BINARY_DIR}/launch-c")
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS "${CMAKE_BINARY_DIR}/launch-cxx")
endif()
else()
# Linux / BSD
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
#fix package build
if(PREFIX)
set(CMAKE_INSTALL_PREFIX ${PREFIX})
else()
set(CMAKE_INSTALL_PREFIX /usr/local)
endif()
endif()
endif()
elseif(WIN32)
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/rundir/${CMAKE_BUILD_TYPE})
endif()
# Define proper compilation flags
if(MSVC)
# Disable Warning C4251, C++20 compatibility, Multi-threaded Builds, Warn Detection, Unwind Semantics, Debug Symbols
set(CMAKE_CXX_FLAGS "/wd4251 /Zc:__cplusplus /std:c++20 /permissive- /W4 /MP /EHsc /Zi")
# Visual Studio: Maximum Optimization, Multi-threaded DLL
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD")
# Visual Studio: No Optimization, Multi-threaded Debug DLL
set(CMAKE_CXX_FLAGS_DEBUG "/Od /MDd")
# Generate PDB, even when in release (So developers can better analyze crash logs)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")
add_compile_definitions(_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING)
elseif(CMAKE_COMPILER_IS_GNUCXX)
# linux/gcc, bsd/gcc, windows/mingw
include(CheckCXXCompilerFlag)
set(CMAKE_CXX_FLAGS_RELEASE "-s -O2")
if(WARNING_AS_ERROR)
set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0 -Wall -Wextra -Werror")
else()
set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0 -Wall -Wextra")
endif()
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++20")
endif()
set(ADDITIONAL_DEBUG_FLAGS
-Wcast-align
-Wmissing-declarations
-Wno-long-long
-Wno-error=extra
-Wno-error=delete-non-virtual-dtor
-Wno-error=sign-compare
-Wno-error=missing-declarations
-Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
)
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})
check_cxx_compiler_flag("${FLAG}" CXX_HAS_WARNING_${FLAG})
if(CXX_HAS_WARNING_${FLAG})
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}")
endif()
endforeach()
# Reduce compiler I/O by using pipes between stages instead of temp files
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
else()
# other: osx/llvm, bsd/llvm
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
if(WARNING_AS_ERROR)
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -Werror -Wno-unused-parameter")
else()
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
endif()
# Reduce compiler I/O by using pipes between stages instead of temp files
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
endif()
# Always included, because they define cockatrice_disable_sanitizers_for() and
# cockatrice_disable_coverage_for() for the vendored trees. They only touch the global flags
# when ENABLE_SANITIZERS or ENABLE_COVERAGE is on.
include(Sanitizers)
include(Coverage)
include(CrashReporting)
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_definitions("-DSFMT_MEXP=19937")
endif()
find_package(Threads REQUIRED)
# Determine 32 or 64 bit build
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_lib_suffix 64)
else()
set(_lib_suffix 32)
endif()
if(DEFINED QTDIR${_lib_suffix})
list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}")
elseif(DEFINED QTDIR)
list(APPEND CMAKE_PREFIX_PATH "${QTDIR}")
elseif(DEFINED ENV{QTDIR${_lib_suffix}})
list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR${_lib_suffix}}")
elseif(DEFINED ENV{QTDIR})
list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR}")
endif()
message(STATUS "Update Translations: ${UPDATE_TRANSLATIONS}")
include(FindQtRuntime)
set(CMAKE_AUTOMOC TRUE)
# Find other needed libraries
find_package(Protobuf CONFIG)
if(NOT Protobuf_FOUND)
find_package(Protobuf REQUIRED)
endif()
if(${Protobuf_VERSION} VERSION_LESS "3.21.0.0" AND NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
message(FATAL_ERROR "No protoc command found!")
endif()
#Find OpenSSL
if(WIN32)
find_package(OpenSSL REQUIRED)
if(OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIRS})
endif()
endif()
#Find VCredist
if(MSVC)
find_package(VCredistRuntime)
endif()
# Package builder
set(CPACK_PACKAGE_CONTACT "Zach Halpern <zach@cockatrice.us>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}")
set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team")
set(CPACK_PACKAGE_DESCRIPTION
"Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline."
)
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_VERSION_FILENAME}")
if(UNIX)
if(APPLE)
set(CPACK_GENERATOR DragNDrop ${CPACK_GENERATOR})
set(CPACK_GENERATOR "DragNDrop")
set(CPACK_DMG_FORMAT "UDBZ")
set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME}")
set(CPACK_SYSTEM_NAME "OSX")
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice/resources/appicon.icns")
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeDMGSetup.script")
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dmgBackground.tif")
set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/SignMacApplications.cmake")
else()
# linux
if(CPACK_GENERATOR STREQUAL "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_MAIN_COMPONENT "cockatrice")
set(CPACK_RPM_PACKAGE_REQUIRES "protobuf, qt6-qtimageformats, qt6-qtmultimedia, qt6-qtsvg, qt6-qttools")
set(CPACK_RPM_PACKAGE_GROUP "Amusements/Games")
set(CPACK_RPM_PACKAGE_URL "http://github.com/Cockatrice/Cockatrice")
# stop directories from making package conflicts
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
/usr/share/applications
/usr/share/icons
/usr/share/icons/hicolor
/usr/share/icons/hicolor/48x48
/usr/share/icons/hicolor/48x48/apps
/usr/share/icons/hicolor/scalable
/usr/share/icons/hicolor/scalable/apps
)
else()
set(CPACK_GENERATOR DEB)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-image-formats-plugins, qt6-qpa-plugins")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
endif()
endif()
elseif(WIN32)
set(CPACK_GENERATOR NSIS ${CPACK_GENERATOR})
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(TRICE_IS_64_BIT 1)
else()
set(TRICE_IS_64_BIT 0)
endif()
# Configure file with custom definitions for NSIS.
configure_file("${COCKATRICE_CMAKE_PATH}/NSIS.definitions.nsh.in" "${PROJECT_BINARY_DIR}/NSIS.definitions.nsh")
# Every runtime DLL a Windows install has to contain, as paths relative to the
# install root. .ci/compile.sh reads this and fails the build when the packaged
# installer is missing one of them, or carries a runtime it does not know about.
#
# All three applications are packaged into the same directory on Windows, so
# this is the union of their Qt module lists, not just the client's: Servatrice
# contributes Qt6Sql.dll and Oracle contributes nothing the client lacks, and a
# client-only manifest leaves Qt6Sql.dll unasserted even though servatrice.exe
# is installed next to cockatrice.exe and cannot start without it.
#
# A statically linked Qt module ships no runtime DLL, so requiring one would
# fail the package check on a static build for no reason. cockatrice/CMakeLists.txt
# installs the runtime of every module listed here, and skips static ones the
# same way, so the two can never disagree.
#
# Only the Qt modules, and that is deliberate. protobuf, Abseil, liblzma, zlib
# and OpenSSL also ride in on the "*.dll" glob, and a client whose runtime is
# absent, stale or of the wrong architecture fails to start with "abseil_dll.dll
# is either not designed to run on Windows or it contains an error", so they
# are exactly the files worth asserting.
#
# Their names cannot be derived from the vcpkg bin directory, which is what a
# first attempt did and what a Windows build immediately refuted: bin holds
# everything vcpkg installed, not everything the applications import. It also
# contains gmock.dll, libprotobuf-lite.dll, libprotoc.dll, legacy.dll and three
# pthreadVC*.dll FindThreads probes, none of which any shipped executable
# links, so requiring them failed a build over a package that was correct.
#
# Deriving them from the executables' own import tables is the real answer,
# and fixup_bundle already walks it to decide what to copy. Until that result
# is available to CMake, requiring the vcpkg half would mean guessing names
# again, which is what produced this bug report in the first place. So assert
# what is derived from the build's own link lists and leave the rest to the
# purge in cmake/NSIS.template.in, which removes every DLL regardless of name.
set(_required_runtime "")
foreach(_qt_module IN LISTS COCKATRICE_QT_MODULES SERVATRICE_QT_MODULES ORACLE_QT_MODULES)
get_target_property(_qt_module_type ${_qt_module} TYPE)
if(_qt_module_type STREQUAL "SHARED_LIBRARY")
string(REGEX REPLACE "^Qt6::" "" _qt_component "${_qt_module}")
list(APPEND _required_runtime "Qt6${_qt_component}.dll")
endif()
endforeach()
list(APPEND _required_runtime "Plugins/platforms/qwindows.dll")
list(REMOVE_DUPLICATES _required_runtime)
list(SORT _required_runtime)
string(REPLACE ";" "\n" _required_runtime "${_required_runtime}")
file(WRITE "${PROJECT_BINARY_DIR}/required-runtime.txt" "${_required_runtime}\n")
# include vcredist into the package; NSIS will take care of running it
if(VCREDISTRUNTIME_FOUND)
install(FILES "${VCREDISTRUNTIME_FILE}" DESTINATION ./)
endif()
endif()
include(CPack)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_interfaces ${CMAKE_BINARY_DIR}/libcockatrice_interfaces)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_protocol ${CMAKE_BINARY_DIR}/libcockatrice_protocol)
if(WITH_CLIENT
OR WITH_SERVER
OR WITH_ORACLE
)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network)
endif()
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_deck_list ${CMAKE_BINARY_DIR}/libcockatrice_deck_list)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_rng ${CMAKE_BINARY_DIR}/libcockatrice_rng)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_card ${CMAKE_BINARY_DIR}/libcockatrice_card)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_utility ${CMAKE_BINARY_DIR}/libcockatrice_utility)
if(WITH_ORACLE OR WITH_CLIENT)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_settings ${CMAKE_BINARY_DIR}/libcockatrice_settings)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_models ${CMAKE_BINARY_DIR}/libcockatrice_models)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_filters ${CMAKE_BINARY_DIR}/libcockatrice_filters)
endif()
if(WITH_SERVER)
add_subdirectory(servatrice)
set(CPACK_INSTALL_CMAKE_PROJECTS "Servatrice;Servatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
if(WITH_CLIENT)
add_subdirectory(cockatrice)
set(CPACK_INSTALL_CMAKE_PROJECTS "Cockatrice;Cockatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
if(WITH_ORACLE)
add_subdirectory(oracle)
set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
if(TEST)
include(CTest)
add_subdirectory(tests)
endif()
if(Qt6_FOUND AND Qt6_VERSION_MINOR GREATER_EQUAL 3)
# Qt6.3+ requires project finalization to support translations
qt6_finalize_project()
endif()