# SPDX-FileCopyrightText: 2017 Niels Ole Salscheider <niels_ole@salscheider-online.de>
#
# SPDX-License-Identifier: CC0-1.0

cmake_minimum_required(VERSION 3.16)
project(qxmpp VERSION 1.17.0)

set(SO_VERSION 10)

# C/C++ settings
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

# Qt
set(QT_VERSION_MAJOR 6)
find_package(Qt6 6.4.0 REQUIRED COMPONENTS Core Network Xml)

# Dependency discovery (optional)
find_package(PkgConfig QUIET)
# OpenSSL 3.0+ (optional, for encryption features)
# OpenSSL 3.0+ uses the Apache-2.0 license which is compatible with LGPL-2.1-or-later
# only via the "or later" clause. Binaries built with this option are effectively LGPL-3.0+.
find_package(OpenSSL 3.0 QUIET)
find_package(ECM 6.11 QUIET NO_MODULE)
if(ECM_FOUND)
    list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
endif()
if(PkgConfig_FOUND)
    pkg_check_modules(OmemoC QUIET IMPORTED_TARGET libomemo-c)
    pkg_check_modules(GStreamer QUIET IMPORTED_TARGET gstreamer-1.0>=1.20)
endif()

include(GNUInstallDirs)

# deprecated options
if(DEFINED BUILD_TESTS)
    set(BUILD_TESTING ${BUILD_TESTS})
endif()

option(BUILD_SHARED "Build shared library." ON)
option(BUILD_TESTING "Build tests." ON)
option(BUILD_DOCUMENTATION "Build HTML API documentation." ON)
option(BUILD_DOCBOOK "Build DOCBOOK documentation." OFF)
option(BUILD_EXAMPLES "Build examples." ON)
option(BUILD_OMEMO "Build the OMEMO module." ${OmemoC_FOUND})
option(WITH_GSTREAMER "Build with GStreamer support for Jingle." ${GStreamer_FOUND})
option(WITH_ENCRYPTION "Build with OpenSSL for OMEMO or encrypted file sharing. Resulting binaries are LGPL-3.0+." ${OPENSSL_FOUND})
option(ENABLE_ASAN "Build with address sanitizer." OFF)

set(QXMPP_TARGET QXmppQt${QT_VERSION_MAJOR})
set(QXMPPOMEMO_TARGET QXmppOmemoQt${QT_VERSION_MAJOR})

add_definitions(
    -DQT_DISABLE_DEPRECATED_BEFORE=0x060900
    -DQT_NO_URL_CAST_FROM_STRING
    -DQT_NO_CAST_TO_ASCII
    -DQT_NO_CAST_FROM_BYTEARRAY
    -DQT_USE_QSTRINGBUILDER
    -DQT_NO_FOREACH
    -DQT_NO_KEYWORDS
)

# Cache compilation results with ccache when available. Every test recompiles the same
# Qt and QXmpp headers (the bulk of a test's compile time), so cached rebuilds are far faster.
option(USE_CCACHE "Use ccache to speed up rebuilds when available." ON)
if(USE_CCACHE)
    find_program(CCACHE_PROGRAM ccache)
    # Leave a launcher the user has set up (another cache, a distributed compiler) alone.
    if(CCACHE_PROGRAM AND NOT CMAKE_C_COMPILER_LAUNCHER AND NOT CMAKE_CXX_COMPILER_LAUNCHER)
        set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
        set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
        message(STATUS "Using ccache: ${CCACHE_PROGRAM}")
    endif()
endif()

# Use a faster linker when one is available. The test executables each statically
# link all of QXmpp (~150 MB in debug builds), so linking dominates their build time
# and the default GNU ld is by far the slowest option.
set(USE_FAST_LINKER "auto" CACHE STRING "Linker to use: auto, mold, lld, gold or default.")
set_property(CACHE USE_FAST_LINKER PROPERTY STRINGS auto mold lld gold default)

# add_link_options() ends up after CMAKE_<TYPE>_LINKER_FLAGS on the link line, so an
# automatically picked linker would override one the user already chose via LDFLAGS,
# CMAKE_<TYPE>_LINKER_FLAGS or CMAKE_LINKER_TYPE. Leave their choice alone; an explicit
# USE_FAST_LINKER=<linker> still wins, because that is a deliberate override.
if(USE_FAST_LINKER STREQUAL "auto")
    if(CMAKE_LINKER_TYPE OR
       "${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" MATCHES "-fuse-ld=|--ld-path=")
        set(USE_FAST_LINKER "default")
        message(STATUS "Using linker: from the given linker flags")
    endif()
endif()

if(NOT USE_FAST_LINKER STREQUAL "default" AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    if(USE_FAST_LINKER STREQUAL "auto")
        set(_linker_candidates mold lld gold)
    else()
        set(_linker_candidates ${USE_FAST_LINKER})
    endif()
    include(CheckCXXSourceCompiles)
    foreach(_linker IN LISTS _linker_candidates)
        set(CMAKE_REQUIRED_LINK_OPTIONS -fuse-ld=${_linker})
        unset(_linker_works_${_linker} CACHE)
        check_cxx_source_compiles("int main() { return 0; }" _linker_works_${_linker})
        unset(CMAKE_REQUIRED_LINK_OPTIONS)
        if(_linker_works_${_linker})
            add_link_options(-fuse-ld=${_linker})
            message(STATUS "Using linker: ${_linker}")
            break()
        endif()
    endforeach()
endif()

# Drop unused code from the binaries. Each test statically links all of QXmpp, but only
# calls a fraction of it. Emitting one section per function/data object (-ffunction-sections
# / -fdata-sections) lets the linker garbage-collect everything the executable never
# references (--gc-sections). This shrinks the test binaries by ~20-40% (~5 GB total) with
# no effect on the debuggability of the code that remains. The three flags work as a unit:
# without the per-section split, --gc-sections has nothing at function granularity to remove.
option(REDUCE_BINARY_SIZE "Garbage-collect unused code from binaries (-ffunction-sections -fdata-sections + --gc-sections)." ON)
# --gc-sections is a GNU ld option; Apple's linker rejects it, so probe it instead
# of assuming every GNU/Clang-like compiler drives a GNU-compatible linker.
if(REDUCE_BINARY_SIZE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    include(CheckCXXSourceCompiles)
    set(CMAKE_REQUIRED_LINK_OPTIONS -Wl,--gc-sections)
    check_cxx_source_compiles("int main() { return 0; }" HAVE_GC_SECTIONS)
    unset(CMAKE_REQUIRED_LINK_OPTIONS)
    if(HAVE_GC_SECTIONS)
        add_compile_options(-ffunction-sections -fdata-sections)
        add_link_options(-Wl,--gc-sections)
    endif()
endif()

# Opt-in line-tables-only debug info (-g1): roughly halves binary size, loses local variable inspection.
option(REDUCED_DEBUG_INFO "Emit line-tables-only debug info (-g1) to shrink binaries and speed up builds." OFF)
if(REDUCED_DEBUG_INFO AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    foreach(_cfg DEBUG RELWITHDEBINFO)
        # Downgrade the full -g (== -g2) that CMake puts in these configurations to -g1.
        string(REGEX REPLACE "(^| )-g( |$)" "\\1-g1\\2" CMAKE_CXX_FLAGS_${_cfg} "${CMAKE_CXX_FLAGS_${_cfg}}")
        string(REGEX REPLACE "(^| )-g( |$)" "\\1-g1\\2" CMAKE_C_FLAGS_${_cfg} "${CMAKE_C_FLAGS_${_cfg}}")
    endforeach()
endif()

# Address sanitizer and undefined behavior sanitizer
if(ENABLE_ASAN)
    add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer)
    add_link_options(-fsanitize=address,undefined)
endif()

# Code coverage (adds the BUILD_COVERAGE option, enabled by the KDE CI)
if(ECM_FOUND)
    include(ECMCoverageOption)
endif()

if(BUILD_OMEMO)
    add_definitions(-DBUILD_OMEMO)
    if(NOT OmemoC_FOUND)
        message(FATAL_ERROR "BUILD_OMEMO requires libomemo-c (via pkg-config)")
    endif()
    if(NOT WITH_ENCRYPTION)
        message(FATAL_ERROR "BUILD_OMEMO requires OpenSSL (WITH_ENCRYPTION=ON)")
    endif()
endif()

if(WITH_ENCRYPTION)
    add_definitions(-DWITH_ENCRYPTION)
endif()

if(WITH_GSTREAMER)
    if(NOT GStreamer_FOUND)
        message(FATAL_ERROR "WITH_GSTREAMER requires gstreamer-1.0 >= 1.20 (via pkg-config)")
    endif()
endif()

add_subdirectory(src)

if(BUILD_TESTING)
    set(CMAKE_CTEST_ARGUMENTS "-j8;--progress")
    enable_testing()
    add_subdirectory(tests)
endif()

if(BUILD_DOCUMENTATION OR BUILD_DOCBOOK)
    add_subdirectory(docs)
endif()

if(BUILD_EXAMPLES)
    add_subdirectory(examples)
endif()

# ECM (optional, for clang-format target and git pre-commit hook)
if(ECM_FOUND)
    include(KDEClangFormat)
    file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.hpp *.c)
    kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})

    include(KDEGitCommitHooks)
    kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
endif()

include(CMakePackageConfigHelpers)

# Normal QXmppQt5/6 package
configure_package_config_file(
    QXmppConfig.cmake.in
    ${CMAKE_CURRENT_BINARY_DIR}/${QXMPP_TARGET}Config.cmake
    INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${QXMPP_TARGET}"
)

write_basic_package_version_file(
    ${CMAKE_CURRENT_BINARY_DIR}/${QXMPP_TARGET}ConfigVersion.cmake
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
)

install(
    FILES ${CMAKE_CURRENT_BINARY_DIR}/${QXMPP_TARGET}Config.cmake
          ${CMAKE_CURRENT_BINARY_DIR}/${QXMPP_TARGET}ConfigVersion.cmake
    DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${QXMPP_TARGET}"
    COMPONENT Devel
)

# Generate QXmppQt5/6.pc
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/qxmpp.pc.in ${CMAKE_CURRENT_BINARY_DIR}/${QXMPP_TARGET}.pc @ONLY)
install(
    FILES ${CMAKE_CURRENT_BINARY_DIR}/${QXMPP_TARGET}.pc
    DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
    COMPONENT Devel
)


