# # InspIRCd -- Internet Relay Chat Daemon # # Copyright (C) 2026 Sadie Powell # # This file is part of InspIRCd. InspIRCd is free software: you can # redistribute it and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation, version 2. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ################################################################################ # build_module: Builds a single module. # # MODULE (STRING): The name of the module. This will be used as the CMake target # name and (with CMAKE_SHARED_LIBRARY_SUFFIX appended) as the # module's filename. # # MODULE_SOURCE(LIST): One or more source files for the module. function(build_module MODULE MODULE_SOURCE) if(NOT ${MODULE} MATCHES "^core_") set(MODULE "m_${MODULE}") endif() add_library(${MODULE} MODULE ${MODULE_SOURCE}) target_compile_definitions(${MODULE} PRIVATE "MODNAME=\"${MODULE}\"" ) target_link_libraries(${MODULE} PRIVATE "inspircd" ) set_target_properties(${MODULE} PROPERTIES APPLE_RESOLVE_SYMBOLS_DYNAMICALLY ON FOLDER "Modules" PREFIX "" ) list(LENGTH MODULE_SOURCE MODULE_SOURCE_COUNT) if(MODULE_SOURCE_COUNT LESS_EQUAL 1) set_target_properties(${MODULE} PROPERTIES UNITY_BUILD OFF ) endif() inline_cmake(${MODULE} ${MODULE_SOURCE}) install_owned( TARGETS ${MODULE} DESTINATION "${MODULE_DIR}" ) endfunction() ################################################################################ # build_modules: Builds all modules in a directory and any subdirectories. # # If a subdirectory contains CMakeLists.txt, add_subdirectory will be called # on it. If it contains a file called main.cpp or a .cpp file with the same # name as its directory, build_module will be called on it. Otherwise, # build_modules (i.e. this function) will be called on it. # # MODULE_SOURCE_DIR (STRING): The directory to build modules in. function(build_modules MODULE_SOURCE_DIR) file(GLOB MODULES CONFIGURE_DEPENDS "${MODULE_SOURCE_DIR}/*") foreach(MODULE IN LISTS MODULES) cmake_path(GET MODULE STEM MODULE_NAME) if(IS_DIRECTORY ${MODULE} AND NOT MODULE_NAME IN_LIST ARGN) if(EXISTS "${MODULE}/CMakeLists.txt") add_subdirectory(${MODULE}) elseif(EXISTS "${MODULE}/${MODULE_NAME}.cpp" OR EXISTS "${MODULE}/main.cpp") file(GLOB_RECURSE MODULE_SOURCE CONFIGURE_DEPENDS "${MODULE}/*.cpp" "${MODULE}/*.h" ) build_module(${MODULE_NAME} "${MODULE_SOURCE}") else() build_modules(${MODULE}) endif() elseif(MODULE MATCHES "\\.cpp$") build_module(${MODULE_NAME} ${MODULE}) endif() endforeach() endfunction() ################################################################################ # configure_path: Configures a path based on the path layout options passed to # CMake. # # NAME (STRING): The name of the variable to store the path in. An absolute # version of the path is also stored in ABSOLUTE_${NAME}. # # SOURCE_DEF (PATH): The default path used on UNIX when neither PORTABLE nor # SYSTEM are set. # # PORTABLE_DEF (PATH): The default path used when the PORTABLE flag is set or # when building on Windows. # # SYSTEM_DEF (PATH): The default path used when the SYSTEM flag is set. # # DESCRIPTION (STRING): A description of the path for the cache entry. function(configure_path) cmake_parse_arguments(PARSE_ARGV 0 "OPT" "" "NAME;SOURCE_DEF;PORTABLE_DEF;SYSTEM_DEF;DESCRIPTION" "") if(NOT DEFINED ${OPT_NAME}) if(PORTABLE OR WIN32) set(${OPT_NAME} ${OPT_PORTABLE_DEF} CACHE PATH ${OPT_DESCRIPTION}) elseif(SYSTEM) set(${OPT_NAME} ${OPT_SYSTEM_DEF} CACHE PATH ${OPT_DESCRIPTION}) else() set(${OPT_NAME} ${OPT_SOURCE_DEF} CACHE PATH ${OPT_DESCRIPTION}) endif() if(PORTABLE OR WIN32) set(ABSOLUTE_${OPT_NAME} ${${OPT_NAME}}) else() cmake_path( APPEND CMAKE_INSTALL_PREFIX "${${OPT_NAME}}" OUTPUT_VARIABLE ABSOLUTE_${OPT_NAME} ) cmake_path(NORMAL_PATH ABSOLUTE_${OPT_NAME}) endif() endif() install_owned( DIRECTORY DESTINATION ${${OPT_NAME}} ) return(PROPAGATE ABSOLUTE_${OPT_NAME} ${OPT_NAME}) endfunction() ################################################################################ # find_id: Retrieves the id and name for a system group or user. # # ID (STRING): The name or numeric id to look up. This MUST already exist on the # system. # # FLAG (STRING): The type of entry to look up; either "g" for a group or "u" for # a user. # # ID_VAR (STRING): The variable to store the retrieved id in. # # NAME_VAR (STRING): The variable to store the retrieved name in. function(find_id ID FLAG ID_VAR NAME_VAR) # `id` has existed since System V so this should always be available. find_program(ID_BINARY "id" REQUIRED) if(SYSTEM AND NOT ID) set(ID 0) # On system-wide installs we default to root ownership. endif() execute_process( COMMAND ${ID_BINARY} "-${FLAG}" ${ID} COMMAND_ERROR_IS_FATAL ANY OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE ${ID_VAR} ) execute_process( COMMAND ${ID_BINARY} "-${FLAG}n" ${ID} COMMAND_ERROR_IS_FATAL ANY OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE ${NAME_VAR} ) return(PROPAGATE ${ID_VAR} ${NAME_VAR}) endfunction() ################################################################################ # inline_cmake: Runs inline CMake code from within a source file. # # The inline CMake code is extracted from a triple-commented block beginning # with "/// BEGIN CMAKE" and ending with "/// END CMAKE". This allows modules # to have custom build configuration without needing a separate CMakeLists.txt. # # TARGET (STRING): The name of the CMake target. This is available to the # target's CMake code, allowing it to call functions like # target_link_libraries on itself. # # FILE (STRING): The path to the source file from which to parse inline CMake # code. function(inline_cmake TARGET FILE) file(STRINGS ${FILE} SRC) set(CODE "") set(IN_CODE OFF) foreach(LINE IN LISTS SRC) if(IN_CODE) string(REGEX REPLACE "^/// " "" CLEAN_LINE ${LINE}) if(CLEAN_LINE MATCHES "^END CMAKE$") cmake_language(EVAL CODE "${CODE}") set(CODE "") set(IN_CODE OFF) else() set(CODE "${CODE}\n${CLEAN_LINE}") endif() elseif(LINE MATCHES "^/// BEGIN CMAKE$") set(IN_CODE ON) endif() endforeach() endfunction() ################################################################################ # install_owned: Installs a target with the destination owned by the group and # user specified in GID and UID. # # DESTINATION (STRING): The location that the targt will be installed to. function(install_owned) install(${ARGN}) if(DEFINED GROUP_ID OR DEFINED USER_ID) cmake_parse_arguments(PARSE_ARGV 0 "OPT" "" "DESTINATION" "") # `chown` has existed since Unix V1 so this should always be available. find_program(CHOWN_BINARY "chown" REQUIRED) # Build the owner for the installed file. set(OWNER "${USER_NAME}") if(GROUP_NAME) string(APPEND OWNER ":${GROUP_NAME}") endif() # Build the destination of the installed file. We need to make sure to # respect DESTDIR so changing the owner still works when packaging. cmake_path( ABSOLUTE_PATH OPT_DESTINATION BASE_DIRECTORY "${CMAKE_INSTALL_PREFIX}" OUTPUT_VARIABLE OPT_DESTINATION ) string(PREPEND OPT_DESTINATION "\${DESTDIR}") install(CODE " execute_process( COMMAND ${CHOWN_BINARY} -R \"${OWNER}\" \"${OPT_DESTINATION}\" ERROR_STRIP_TRAILING_WHITESPACE ERROR_VARIABLE CHOWN_ERROR RESULT_VARIABLE CHOWN_RESULT ) if(CHOWN_RESULT) message(FATAL_ERROR \"Unable to set owner of ${OPT_DESTINATION} to ${OWNER} - \${CHOWN_ERROR}\") endif() ") endif() endfunction() ################################################################################ # update_cache: Updates the value of a cache variable whilst keeping the same # type and description. # # VAR_NAME (STRING): The the cache variable to update. # # VAR_VALUE (STRING): The new value for the cache variable. function(update_cache VAR_NAME VAR_VALUE) get_property(OLD_DESC CACHE ${VAR_NAME} PROPERTY HELPSTRING) get_property(OLD_TYPE CACHE ${VAR_NAME} PROPERTY TYPE) if(NOT OLD_TYPE) set(OLD_TYPE "STRING") # Fall back to string endif() set(${VAR_NAME} "${VAR_VALUE}" CACHE ${OLD_TYPE} "${OLD_DESC}" FORCE) endfunction() ################################################################################ # target_report_error: Enables a CMake target to report an error that is treated # as non-fatal during a dry-run, but becomes fatal whilst trying to build the # target. # # TARGET (STRING): The name of the CMake target to which the error refers. This # is used for diagnostic reporting and does not need to exist # yet. # # MESSAGE (STRING): The specific error message to report. function(target_report_error TARGET MESSAGE) if(FIND_DEPENDENCY_DRY_RUN) update_cache(FIND_DEPENDENCY_FOUND NO) set(MESSAGE_TYPE "STATUS") else() set(MESSAGE_TYPE "FATAL_ERROR") endif() message(${MESSAGE_TYPE} "${TARGET}: ${MESSAGE}") endfunction() ################################################################################ # target_require_package: Finds a package that a target depends on and links # to it. # # On Windows, this uses find_package to try and find the package in the Conan # cache. On other platforms, it uses pkg-config to try and find the package # from the system package manager. # # PKGCONF_NAMES (STRING): A space-delimited list of pkg-config package names for # use on non-Windows systems. The first available one is used. # # CMAKE_NAME (STRING): The CMake package name for use on Windows systems. # # LINK_TARGET (STRING): The name of the CMake import target to link against the # module if the package is available. For pkg-config, this is an alias target to # the pkg-config target; on Windows, this is the target provided by Conan. function(target_require_package TARGET PKGCONF_NAMES CMAKE_NAME LINK_TARGET) if(NOT TARGET ${LINK_TARGET}) if(WIN32) find_package(${CMAKE_NAME}) elseif(PkgConfig_FOUND) string(REPLACE " " ";" PKGCONF_NAMES "${PKGCONF_NAMES}") foreach(PKGCONF_NAME IN LISTS PKGCONF_NAMES) pkg_check_modules(${PKGCONF_NAME} IMPORTED_TARGET ${PKGCONF_NAME}) if(${PKGCONF_NAME}_FOUND) add_library(${LINK_TARGET} ALIAS PkgConfig::${PKGCONF_NAME}) break() endif() endforeach() endif() endif() if(TARGET ${LINK_TARGET}) update_cache(FIND_DEPENDENCY_FOUND YES) if(NOT FIND_DEPENDENCY_DRY_RUN) target_link_libraries(${TARGET} PRIVATE ${LINK_TARGET}) endif() else() update_cache(FIND_DEPENDENCY_FOUND NO) if(WIN32) target_report_error(${TARGET} "Unable to find ${CMAKE_NAME} with find_package!") else() target_report_error(${TARGET} "Unable to find ${PKGCONF_NAMES} with pkg-config!") endif() endif() endfunction()