cmake_minimum_required(VERSION 3.0) project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 9) set(${PROJECT_NAME}_VERSION_MINOR 0) set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) message(FATAL_ERROR "GCC version must be at least 5.0.") endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.4) message(FATAL_ERROR "Clang version must be at least 3.4.") endif() endif() if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type (Release/Debug/RelWithDebInfo/MinSizeRel)" FORCE) endif() # ## Find optional instrumentation libraries that will be used in debug only # find_library(LIBASAN NAMES asan libasan.so.4 libasan.so.3 libasan.so.2 libasan.so.1) find_library(LIBUBSAN NAMES ubsan libubsan.so.0) # ## Set various debug flags (instrumentation libs, coverage, …) # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra -Wconversion -fvisibility=hidden -fvisibility-inlines-hidden") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage") endif() if(LIBASAN) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") endif() if(LIBUBSAN) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined") endif() # ## Set the software version, archive name, RPM name etc # set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}) set(RPM_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}) if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES ".+") set(ARCHIVE_NAME ${ARCHIVE_NAME}${${PROJECT_NAME}_VERSION_SUFFIX}) set(RPM_VERSION ${RPM_VERSION}${${PROJECT_NAME}_VERSION_SUFFIX}) endif() if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "^~dev$") # If we are on a dev version, append the hash of the current git HEAD to # the version include(FindGit) if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") execute_process(COMMAND git --git-dir=${CMAKE_SOURCE_DIR}/.git rev-parse --short HEAD OUTPUT_VARIABLE GIT_REVISION OUTPUT_STRIP_TRAILING_WHITESPACE) if(GIT_REVISION) set(${PROJECT_NAME}_VERSION_SUFFIX "${${PROJECT_NAME}_VERSION_SUFFIX} (${GIT_REVISION})") set(ARCHIVE_NAME ${ARCHIVE_NAME}${GIT_REVISION}) set(RPM_VERSION ${RPM_VERSION}${GIT_REVISION}) endif() endif() endif() set(SOFTWARE_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}${${PROJECT_NAME}_VERSION_SUFFIX}) # ## The rule that generates the documentation # execute_process(COMMAND "date" "+%Y-%m-%d" OUTPUT_VARIABLE DOC_DATE OUTPUT_STRIP_TRAILING_WHITESPACE) set(MAN_PAGE ${CMAKE_CURRENT_BINARY_DIR}/doc/${PROJECT_NAME}.1) set(DOC_PAGE ${CMAKE_CURRENT_SOURCE_DIR}/doc/${PROJECT_NAME}.1.rst) if (NOT PANDOC_EXECUTABLE) find_program(PANDOC_EXECUTABLE NAMES pandoc DOC "The pandoc software, to build the man page from the rst documentation") if(PANDOC_EXECUTABLE) message(STATUS "Found Pandoc: ${PANDOC_EXECUTABLE}") set(WITH_DOC true) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/) add_custom_command(OUTPUT ${MAN_PAGE} COMMAND ${PANDOC_EXECUTABLE} -M date="${DOC_DATE}" -s -t man ${DOC_PAGE} -o ${MAN_PAGE} DEPENDS ${DOC_PAGE}) add_custom_target(doc ALL DEPENDS ${MAN_PAGE}) else() message(STATUS "Pandoc not found, documentation cannot be built") endif() endif() mark_as_advanced(PANDOC_EXECUTABLE) # ## Set this search path for cmake, to find our custom search modules # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") find_package(ICONV REQUIRED) find_package(LIBUUID REQUIRED) find_package(EXPAT REQUIRED) # ## Find all the libraries (optional or not) # if(WITH_LIBIDN) find_package(LIBIDN REQUIRED) elseif(NOT WITHOUT_LIBIDN) find_package(LIBIDN) endif() if(WITH_SYSTEMD) find_package(SYSTEMD REQUIRED) elseif(NOT WITHOUT_SYSTEMD) find_package(SYSTEMD) endif() if(WITH_BOTAN) find_package(BOTAN REQUIRED) elseif(NOT WITHOUT_BOTAN) find_package(BOTAN) endif() if(NOT BOTAN_FOUND) find_package(GCRYPT REQUIRED) endif() if(WITH_UDNS) find_package(UDNS REQUIRED) elseif(NOT WITHOUT_UDNS) find_package(UDNS) endif() if(WITH_SQLITE3) find_package(SQLITE3 REQUIRED) elseif(NOT WITHOUT_SQLITE3) find_package(SQLITE3) endif() if(WITH_POSTGRESQL) find_package(PQ REQUIRED) elseif(NOT WITHOUT_POSTGRESQL) find_package(PQ) endif() # ## Set all the include directories, depending on what libraries are used # include_directories(${EXPAT_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIRS}) include_directories(${LIBUUID_INCLUDE_DIRS}) if(SYSTEMD_FOUND) include_directories(${SYSTEMD_INCLUDE_DIRS}) endif() if(BOTAN_FOUND) include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) endif() if(UDNS_FOUND) include_directories(${UDNS_INCLUDE_DIRS}) endif() # To be able to include the config.h and other files generated by cmake include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") include_directories("${CMAKE_CURRENT_BINARY_DIR}/") # ## Define all the modules # file(GLOB source_utils src/utils/*.[hc]pp) add_library(utils OBJECT ${source_utils}) file(GLOB source_irc src/irc/*.[hc]pp) add_library(irc OBJECT ${source_irc}) file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp OBJECT ${source_xmpp}) file(GLOB source_identd src/identd/*.[hc]pp) add_library(identd OBJECT ${source_identd}) file(GLOB source_bridge src/bridge/*.[hc]pp) add_library(bridge OBJECT ${source_bridge}) file(GLOB source_config src/config/*.[hc]pp) add_library(config OBJECT ${source_config}) file(GLOB source_logger src/logger/*.[hc]pp) add_library(logger OBJECT ${source_logger}) file(GLOB source_network src/network/*.[hc]pp) add_library(network OBJECT ${source_network}) option(DEBUG_SQL_QUERIES "If set to true, every SQL statement executed will be logged and timed" OFF) if(SQLITE3_FOUND OR PQ_FOUND) file(GLOB source_database src/database/*.[hc]pp) add_library(database OBJECT ${source_database}) if(SQLITE3_FOUND) include_directories(database ${SQLITE3_INCLUDE_DIRS}) endif() if(PQ_FOUND) include_directories(database ${PQ_INCLUDE_DIRS}) endif() set(USE_DATABASE TRUE) endif() # ## Define the executables # ## main add_executable(${PROJECT_NAME} src/main.cpp $ $ $ $ $ $ $ $) ## test_suite file(GLOB source_tests tests/*.cpp) add_executable(test_suite ${source_tests} $ $ $ $ $ $ $ $) set_target_properties(test_suite PROPERTIES EXCLUDE_FROM_ALL TRUE) if(USE_DATABASE) target_sources(${PROJECT_NAME} PRIVATE $) target_sources(test_suite PRIVATE $) endif() # ## Link the executables with their libraries # target_link_libraries(${PROJECT_NAME} ${ICONV_LIBRARIES} ${LIBUUID_LIBRARIES} ${EXPAT_LIBRARY}) target_link_libraries(test_suite ${ICONV_LIBRARIES} ${LIBUUID_LIBRARIES} ${EXPAT_LIBRARY}) if(SYSTEMD_FOUND) target_link_libraries(${PROJECT_NAME} ${SYSTEMD_LIBRARIES}) target_link_libraries(test_suite ${SYSTEMD_LIBRARIES}) endif() if(BOTAN_FOUND) target_link_libraries(${PROJECT_NAME} ${BOTAN_LIBRARIES}) target_link_libraries(test_suite ${BOTAN_LIBRARIES}) elseif(GCRYPT_FOUND) target_link_libraries(${PROJECT_NAME} ${GCRYPT_LIBRARIES}) target_link_libraries(test_suite ${GCRYPT_LIBRARIES}) endif() if(UDNS_FOUND) target_link_libraries(${PROJECT_NAME} ${UDNS_LIBRARIES}) target_link_libraries(test_suite ${UDNS_LIBRARIES}) endif() if(LIBIDN_FOUND) target_link_libraries(${PROJECT_NAME} ${LIBIDN_LIBRARIES}) target_link_libraries(test_suite ${LIBIDN_LIBRARIES}) endif() if(USE_DATABASE) if(SQLITE3_FOUND) target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES}) target_link_libraries(test_suite ${SQLITE3_LIBRARIES}) endif() if(PQ_FOUND) target_link_libraries(${PROJECT_NAME} ${PQ_LIBRARIES}) target_link_libraries(test_suite ${PQ_LIBRARIES}) endif() endif() # Define a __FILENAME__ macro with the relative path (from the base project directory) # of each source file file(GLOB_RECURSE source_all src/*.[hc]pp tests/*.[hc]pp) foreach(file ${source_all}) file(RELATIVE_PATH shorter_file ${CMAKE_CURRENT_SOURCE_DIR} ${file}) set_property(SOURCE ${file} APPEND PROPERTY COMPILE_DEFINITIONS __FILENAME__="${shorter_file}") endforeach() # ## Add a rule to download the catch unit test framework # include(ExternalProject) ExternalProject_Add(catch GIT_REPOSITORY "https://lab.louiz.org/louiz/Catch.git" PREFIX "external" UPDATE_COMMAND "" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" ) set_target_properties(catch PROPERTIES EXCLUDE_FROM_ALL TRUE) ExternalProject_Get_Property(catch SOURCE_DIR) if(NOT EXISTS ${CMAKE_SOURCE_DIR}/tests/catch.hpp) target_include_directories(test_suite PUBLIC "${SOURCE_DIR}/single_include/" ) add_dependencies(test_suite catch) endif() # ## Add some custom rules to launch the tests # add_custom_target(check COMMAND "test_suite" DEPENDS test_suite biboumi) set_target_properties(check PROPERTIES EXCLUDE_FROM_ALL TRUE) add_custom_target(e2e COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" DEPENDS biboumi) set_target_properties(e2e PROPERTIES EXCLUDE_FROM_ALL TRUE) add_custom_target(e2e_valgrind COMMAND "E2E_BIBOUMI_SUPP_DIR=${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" "E2E_BIBOUMI_VALGRIND=1" "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" DEPENDS biboumi) if(CMAKE_BUILD_TYPE MATCHES Debug) include(CodeCoverage) SETUP_TARGET_FOR_COVERAGE(coverage_check ./test_suite coverage_test_suite) add_dependencies(coverage_check test_suite) SETUP_TARGET_FOR_COVERAGE(coverage_e2e python3 coverage_e2e ${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/) add_dependencies(coverage_e2e biboumi) ADD_CUSTOM_TARGET(coverage COMMAND ${LCOV_PATH} -a coverage_e2e.info -a coverage_test_suite.info -o coverage_total.info COMMAND ${GENHTML_PATH} -o coverage_total coverage_total.info WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endif() add_custom_target(everything DEPENDS test_suite biboumi) # ## Install target # install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) install(FILES ${MAN_PAGE} DESTINATION share/man/man1 OPTIONAL COMPONENT documentation) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/biboumi.service DESTINATION lib/systemd/system COMPONENT init) file(GLOB policy_files conf/*policy.txt) install(FILES ${policy_files} DESTINATION /etc/biboumi COMPONENT configuration) # ## Dist target ## Generate a release tarball from the git sources # add_custom_command(OUTPUT ${ARCHIVE_NAME}.tar.xz COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD^{tree} > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar # Append this specific file that is not part of the git repo COMMAND tar -rf ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar -P ${SOURCE_DIR}/single_include/catch.hpp --xform 's|/.*/|${ARCHIVE_NAME}/tests/|g' # Remove a potential existing archive COMMAND rm -f ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz # Compress the archive COMMAND xz ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "${ARCHIVE_NAME}.tar.xz created." WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) add_custom_target(dist DEPENDS ${ARCHIVE_NAME}.tar.xz DEPENDS catch) add_custom_target(rpm DEPENDS dist COMMAND mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} COMMAND rpmbuild --define "_topdir `pwd`/rpmbuild/" --define "_sourcedir `pwd`" -ba biboumi.spec ) # ## Set some variables that will be used in the cmake-generated files # set(SYSTEMD_SERVICE_TYPE_DOCSTRING "The value used as the Type= in the systemd unit file.") set(WATCHDOG_SEC_DOCSTRING "The value used as WatchdogSec= in the systemd unit file.") if(SYSTEMD_FOUND) set(SYSTEMD_SERVICE_TYPE "notify" CACHE STRING ${SYSTEMD_SERVICE_TYPE_DOCSTRING}) set(WATCHDOG_SEC "20" CACHE STRING ${WATCHDOG_SEC_DOCSTRING}) else() set(SYSTEMD_SERVICE_TYPE "simple" CACHE STRING ${SYSTEMD_SERVICE_TYPE_DOCSTRING}) set(WATCHDOG_SEC "" CACHE STRING ${WATCHDOG_SEC_DOCSTRING}) endif() set(SERVICE_USER_DOCSTRING "The value used as the User= in the systemd unit file.") if(NOT DEFINED SERVICE_USER) set(SERVICE_USER "nobody" CACHE STRING ${SERVICE_USER_DOCSTRING}) endif() set(SERVICE_GROUP_DOCSTRING "The value used as the Group= in the systemd unit file.") if(NOT DEFINED SERVICE_GROUP) set(SERVICE_GROUP "nobody" CACHE STRING ${SERVICE_GROUP_DOCSTRING}) endif() # Force the format of the date output set(ENV{LANG} "C") execute_process(COMMAND "date" "+%a %b %d %Y" OUTPUT_VARIABLE RPM_DATE OUTPUT_STRIP_TRAILING_WHITESPACE) unset(ENV{LANG}) set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING}) else() set(POLLER "POLL" CACHE STRING ${POLLER_DOCSTRING}) endif() if((NOT ${POLLER} MATCHES "POLL") AND (NOT ${POLLER} MATCHES "EPOLL")) message(FATAL_ERROR "POLLER must be either POLL or EPOLL") endif() # ## Check if we have std::get_time and put_time # include(CheckCXXSourceCompiles) check_cxx_source_compiles(" #include int main() { std::get_time(nullptr, \"\"); }" HAS_GET_TIME) mark_as_advanced(HAS_GET_TIME) check_cxx_source_compiles(" #include int main() { std::put_time(nullptr, \"\"); }" HAS_PUT_TIME) mark_as_advanced(HAS_PUT_TIME) configure_file(unit/biboumi.service.cmake biboumi.service) configure_file(packaging/biboumi.spec.cmake biboumi.spec) configure_file(src/biboumi.h.cmake src/biboumi.h)