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/context.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 what the build produced: the artifact directories, the
# generated documentation, and the test scratch areas.
#
# The generated build system stays. CMakeCache.txt, the makefiles,
# CMakeFiles/, and the CMake file API directory under .cmake/ are what an
# IDE reads to know the project's targets exist; deleting them leaves the
# directory unconfigured, so every target in the IDE stops resolving until
# the project is reloaded. Removing the build system is what configuring a
# fresh directory is for.
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES
        ${DPM_TEST_RESULTS_DIR}
        ${CMAKE_BINARY_DIR}/Testing
        ${CMAKE_BINARY_DIR}/bin
        ${CMAKE_BINARY_DIR}/lib
        ${CMAKE_BINARY_DIR}/modules
        ${DPM_TEST_DIR}
        ${DPM_DOCS_DIR}
)

# ---------------------------------------------------------------------
# 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)
