cmake_minimum_required(VERSION 3.22)

# libdpm-core.so's version is declared in src/core/version.cpp, the file
# that reports it. The build reads that declaration rather than carrying
# its own copy, so the soname, the artifact filenames, and the version
# the library answers with all derive from one line of source.
file(READ src/core/version.cpp DPM_VERSION_SOURCE)
string(REGEX MATCH "DPM_CORE_VERSION_STR[ \t]+\"([0-9]+\\.[0-9]+\\.[0-9]+)\""
        DPM_VERSION_MATCH "${DPM_VERSION_SOURCE}")
if(NOT CMAKE_MATCH_1)
    message(FATAL_ERROR
            "No DPM_CORE_VERSION_STR found in src/core/version.cpp")
endif()

project(dpm-core VERSION ${CMAKE_MATCH_1} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# ---------------------------------------------------------------------
# libdpm-core.so — the library (C ABI)
# ---------------------------------------------------------------------
add_library(dpm-core SHARED
        src/core/conf.cpp
        src/core/context.cpp
        src/core/logging.cpp
        src/core/modules.cpp
        src/core/version.cpp
)

target_include_directories(dpm-core PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
)

# The public C API is the library's entire exported surface; internals
# stay hidden. The version script pins the export list and versions the
# symbols.
target_compile_options(dpm-core PRIVATE
        -fvisibility=hidden
        -fvisibility-inlines-hidden
)
target_link_options(dpm-core PRIVATE
        -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/core/libdpm-core.map
)
set_property(TARGET dpm-core APPEND PROPERTY
        LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/core/libdpm-core.map
)

target_link_libraries(dpm-core PRIVATE ${CMAKE_DL_LIBS})

set_target_properties(dpm-core PROPERTIES
        VERSION ${PROJECT_VERSION}
        SOVERSION ${PROJECT_VERSION_MAJOR}
        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)

# ---------------------------------------------------------------------
# dpm — the CLI (thin client of libdpm-core)
# ---------------------------------------------------------------------
add_executable(dpm src/cli/dpm.cpp)

target_link_libraries(dpm PRIVATE dpm-core)

set_target_properties(dpm PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
        BUILD_RPATH "$ORIGIN/../lib"
)

# ---------------------------------------------------------------------
# info — the bundled module (tests and reports libdpm-core functionality)
# ---------------------------------------------------------------------
add_library(info MODULE
        src/bundled-modules/info/info.cpp
        src/bundled-modules/info/src/commands.cpp
)

target_include_directories(info PRIVATE src/bundled-modules/info/include)

# The one permitted link dependency: libdpm-core.
target_link_libraries(info PRIVATE dpm-core)

set_target_properties(info PROPERTIES
        PREFIX ""
        SUFFIX ".so"
        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/modules
)

# The module bundles with libdpm-core, so building the CLI builds it too: after a
# clean, running dpm from the build tree finds a module directory that is
# populated. This orders the build only — the CLI does not link the module.
add_dependencies(dpm info)

# ---------------------------------------------------------------------
# Tests
#
# The test targets and cases are defined in tests/CMakeLists.txt. The
# paths they build into are named here, above that directory, so each is
# written once and the clean rule below refers to the same values.
# ---------------------------------------------------------------------
enable_testing()

set(DPM_TEST_DIR         ${CMAKE_BINARY_DIR}/tests)
set(DPM_TEST_FIXTURE_DIR ${DPM_TEST_DIR}/fixtures)
set(DPM_TEST_RESULTS_DIR ${CMAKE_BINARY_DIR}/test-results)

# CTest's scratch area is named by its BuildDirectory configuration option.
# Writing it into DartConfiguration.tcl puts it where ctest reads it on every
# invocation — the test target, a bare ctest, and the IDE's test discovery
# alike — rather than only the ones the build drives.
file(WRITE ${CMAKE_BINARY_DIR}/DartConfiguration.tcl
        "BuildDirectory: ${DPM_TEST_RESULTS_DIR}\n")

add_subdirectory(tests)

# ---------------------------------------------------------------------
# Documentation
#
# The Doxygen configuration and the 'docs' target are defined in
# docs/CMakeLists.txt. The paths it builds into are named here, above
# that directory, so each is written once and the clean rule below
# refers to the same values.
#
# The subdirectory is given its own binary directory so that CMake's
# scaffolding stays out of the documentation output.
# ---------------------------------------------------------------------
set(DPM_DOCS_DIR      ${CMAKE_BINARY_DIR}/docs)
set(DPM_DOCS_TMP_DIR  ${DPM_DOCS_DIR}/tmp)
set(DPM_DOCS_PDF_DIR  ${DPM_DOCS_DIR}/pdf)
set(DPM_DOCS_HTML_DIR ${DPM_DOCS_DIR}/html)

add_subdirectory(docs ${CMAKE_BINARY_DIR}/docs-build)

# ---------------------------------------------------------------------
# Clean
# ---------------------------------------------------------------------
# clean removes everything the build produced and leaves the configured
# build system in place, so the next build proceeds from the directory
# as it stands.
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES
        # Build artifacts.
        ${CMAKE_BINARY_DIR}/bin
        ${CMAKE_BINARY_DIR}/lib
        ${CMAKE_BINARY_DIR}/modules
        ${DPM_TEST_DIR}
        ${DPM_DOCS_DIR}

        # Test scratch areas.
        ${DPM_TEST_RESULTS_DIR}
        ${CMAKE_BINARY_DIR}/Testing
)

# distclean empties the build directory, the generated build system
# included, leaving what a freshly created directory holds: nothing.
#
# It is a terminal operation. Nothing inside the directory can drive a
# build afterwards, so the next step is `cmake -B <dir>` from outside
# it. Use it to prove a change against a tree carrying no state from
# earlier runs.
add_custom_target(distclean
        COMMAND ${CMAKE_COMMAND} -E rm -rf
                ${CMAKE_BINARY_DIR}/bin
                ${CMAKE_BINARY_DIR}/lib
                ${CMAKE_BINARY_DIR}/modules
                ${DPM_TEST_DIR}
                ${DPM_DOCS_DIR}
                ${DPM_TEST_RESULTS_DIR}
                ${CMAKE_BINARY_DIR}/Testing
                ${CMAKE_BINARY_DIR}/CMakeFiles
                ${CMAKE_BINARY_DIR}/CMakeCache.txt
                ${CMAKE_BINARY_DIR}/Makefile
                ${CMAKE_BINARY_DIR}/cmake_install.cmake
                ${CMAKE_BINARY_DIR}/CTestTestfile.cmake
                ${CMAKE_BINARY_DIR}/DartConfiguration.tcl
                ${CMAKE_BINARY_DIR}/.cmake
                ${CMAKE_BINARY_DIR}/CMakeDoxyfile.in
                ${CMAKE_BINARY_DIR}/CMakeDoxygenDefaults.cmake
                ${CMAKE_BINARY_DIR}/docs-build
        COMMENT "Emptying the build directory"
)

# ---------------------------------------------------------------------
# Installation
# ---------------------------------------------------------------------
install(TARGETS dpm RUNTIME DESTINATION bin)
install(TARGETS dpm-core LIBRARY DESTINATION lib)
install(TARGETS info LIBRARY DESTINATION lib/dpm/modules)
install(DIRECTORY include/dpm DESTINATION include)
install(FILES data/core.conf DESTINATION /etc/dpm/conf.d)
