From 02844e598f7b93c7b40eeeb8da7b5b37b29980a0 Mon Sep 17 00:00:00 2001 From: Peter M. Groen Date: Fri, 30 Jan 2026 02:03:19 +0100 Subject: [PATCH] Added test to library --- .gitignore | 1 + CMakeLists.txt | 39 +++++++++++++++++++++++++++++++++++++++ cmake/COPYING.OSDEV | 21 +++++++++++++++++++++ cmake/FindGMock.cmake | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmake/FindJsoncpp.cmake | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ cmake/FindYamlCpp.cmake | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ cmake/application.cmake | 38 ++++++++++++++++++++++++++++++++++++++ cmake/application.post_install.inc.cmake.in | 14 ++++++++++++++ cmake/application.post_uninstall.inc.cmake.in | 5 +++++ cmake/artifacts.cmake | 40 ++++++++++++++++++++++++++++++++++++++++ cmake/compiler.cmake | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmake/config.cmake.in | 22 ++++++++++++++++++++++ cmake/installation.cmake | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmake/ld.so.conf.d.cmake.in | 1 + cmake/library.cmake | 41 +++++++++++++++++++++++++++++++++++++++++ cmake/library.post_install.inc.cmake.in | 16 ++++++++++++++++ cmake/packaging.cmake | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmake/projectheader.cmake | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmake/qtmoc.cmake | 33 +++++++++++++++++++++++++++++++++ cmake/qtuic.cmake | 39 +++++++++++++++++++++++++++++++++++++++ cmake/service.cmake.in | 11 +++++++++++ cmake/sync.sh | 33 +++++++++++++++++++++++++++++++++ cmake/targetprops.cmake | 7 +++++++ include/daemonbase.h | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/daemonconfig.h | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/daemonlog.h | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 18 ++++++++++++++++++ test/MyDaemonExample.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 28 files changed, 1773 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/COPYING.OSDEV create mode 100644 cmake/FindGMock.cmake create mode 100644 cmake/FindJsoncpp.cmake create mode 100644 cmake/FindYamlCpp.cmake create mode 100644 cmake/application.cmake create mode 100644 cmake/application.post_install.inc.cmake.in create mode 100644 cmake/application.post_uninstall.inc.cmake.in create mode 100644 cmake/artifacts.cmake create mode 100644 cmake/compiler.cmake create mode 100644 cmake/config.cmake.in create mode 100644 cmake/installation.cmake create mode 100644 cmake/ld.so.conf.d.cmake.in create mode 100644 cmake/library.cmake create mode 100644 cmake/library.post_install.inc.cmake.in create mode 100644 cmake/packaging.cmake create mode 100644 cmake/projectheader.cmake create mode 100644 cmake/qtmoc.cmake create mode 100644 cmake/qtuic.cmake create mode 100644 cmake/service.cmake.in create mode 100755 cmake/sync.sh create mode 100644 cmake/targetprops.cmake create mode 100644 include/daemonbase.h create mode 100644 include/daemonconfig.h create mode 100644 include/daemonlog.h create mode 100644 test/CMakeLists.txt create mode 100644 test/MyDaemonExample.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..324b9a2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.30) +project(daemonbase VERSION 0.1 LANGUAGES CXX) +include_guard(GLOBAL) + +if(EXISTS ${CMAKE_SOURCE_DIR}/cmake) + # Use the cmake directives for compilers etc... + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) +else() + message("Cmake source dir : ${CMAKE_SOURCE_DIR}") +endif() + +include_directories( SYSTEM + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +include(compiler) + +set(SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/include/daemonbase.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/daemonconfig.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/daemonlog.h +) + +include(library) +add_libraries() + +include(installation) +install_component() + +include(packaging) +package_component() + +link_directories( + ${CMAKE_BINARY_DIR}/lib +) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) + +add_subdirectory(test) \ No newline at end of file diff --git a/cmake/COPYING.OSDEV b/cmake/COPYING.OSDEV new file mode 100644 index 0000000..bc7bb9a --- /dev/null +++ b/cmake/COPYING.OSDEV @@ -0,0 +1,21 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ \ No newline at end of file diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake new file mode 100644 index 0000000..0e6d4c8 --- /dev/null +++ b/cmake/FindGMock.cmake @@ -0,0 +1,72 @@ +#.rst: +# FindGMock +# --------- +# +# Locate the Google C++ Mocking Framework. +# +# Defines the following variables: +# +# :: +# +# GMOCK_FOUND - Found the Google Testing framework +# GMOCK_INCLUDE_DIRS - Include directories +# +# +# +# Also defines the gmock source path +# +# :: +# +# GMOCK_SOURCE_DIR - directory containing the gmock sources +# +# +# +# Accepts the following variables as input: +# +# :: +# +# GMOCK_ROOT - (as a CMake or environment variable) +# The root directory of the gmock install prefix +# +# +# +# +# Example Usage: +# +# :: +# +# enable_testing() +# find_package(GMock REQUIRED) +# include_directories(${GMOCK_INCLUDE_DIRS}) +# +# +# +# :: +# +# add_executable(foo ${GMOCK_SOURCE_DIR}/gmock-all.cc foo.cc) +# target_link_libraries(foo) +# +# + +find_path(GMOCK_INCLUDE_DIR gmock/gmock.h + HINTS + $ENV{GMOCK_ROOT}/include + ${GMOCK_ROOT}/include +) +mark_as_advanced(GMOCK_INCLUDE_DIR) + +find_path(GMOCK_SOURCE_DIR gmock-all.cc + HINTS + $ENV{GMOCK_ROOT}/src/gmock + ${GMOCK_ROOT}/src/gmock + PATHS + ${GMOCK_INCLUDE_DIR}/../src/gmock +) +mark_as_advanced(GMOCK_SOURCE_DIR) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMock DEFAULT_MSG GMOCK_INCLUDE_DIR GMOCK_SOURCE_DIR) + +if(GMOCK_FOUND) + set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) +endif() + diff --git a/cmake/FindJsoncpp.cmake b/cmake/FindJsoncpp.cmake new file mode 100644 index 0000000..b7338a5 --- /dev/null +++ b/cmake/FindJsoncpp.cmake @@ -0,0 +1,53 @@ +# Locate jsoncpp +# +# This module defines +# JSONCPP_FOUND, if false, do not try to link to jsoncpp +# JSONCPP_LIBRARY, where to find jsoncpp +# JSONCPP_INCLUDE_DIR, where to find json.h +# +# By default, the dynamic libraries of jsoncpp will be found. To find the static ones instead, +# you must set the JSONCPP_STATIC_LIBRARY variable to TRUE before calling find_package(Jsoncpp ...). +# +# If jsoncpp is not installed in a standard path, you can use the JSONCPP_DIR CMake variable +# to tell CMake where jsoncpp is. + +set(JSONCPP_FOUND 0) + +# attempt to find static library first if this is set +if(JSONCPP_STATIC_LIBRARY) + set(JSONCPP_STATIC libjsoncpp.a) +endif() + +# find the jsoncpp include directory +find_path(JSONCPP_INCLUDE_DIR json/json.h + PATHS + ~/Library/Frameworks/jsoncpp/include/ + /Library/Frameworks/jsoncpp/include/ + /usr/local/include/ + /usr/include/ + /sw/jsoncpp/ # Fink + /opt/local/jsoncpp/ # DarwinPorts + /opt/csw/jsoncpp/ # Blastwave + /opt/jsoncpp/ + ${JSONCPP_DIR}/include/ + PATH_SUFFIXES jsoncpp) + +# find the jsoncpp library +find_library(JSONCPP_LIBRARY + NAMES ${JSONCPP_STATIC} jsoncpp + PATH_SUFFIXES lib64 lib + PATHS ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw + /opt/local + /opt/csw + /opt + ${JSONCPP_DIR}/lib) + +# handle the QUIETLY and REQUIRED arguments and set JSONCPP_FOUND to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(JSONCPP DEFAULT_MSG JSONCPP_INCLUDE_DIR JSONCPP_LIBRARY) +mark_as_advanced(JSONCPP_INCLUDE_DIR JSONCPP_LIBRARY) +message("JSONCPP_FOUND = ${JSONCPP_FOUND}, includedir = ${JSONCPP_INCLUDE_DIR}, lib = ${JSONCPP_LIBRARY}") diff --git a/cmake/FindYamlCpp.cmake b/cmake/FindYamlCpp.cmake new file mode 100644 index 0000000..1ec9798 --- /dev/null +++ b/cmake/FindYamlCpp.cmake @@ -0,0 +1,50 @@ +# Locate yaml-cpp +# +# This module defines +# YAMLCPP_FOUND, if false, do not try to link to yaml-cpp +# YAMLCPP_LIBRARY, where to find yaml-cpp +# YAMLCPP_INCLUDE_DIR, where to find yaml.h +# +# By default, the dynamic libraries of yaml-cpp will be found. To find the static ones instead, +# you must set the YAMLCPP_STATIC_LIBRARY variable to TRUE before calling find_package(YamlCpp ...). +# +# If yaml-cpp is not installed in a standard path, you can use the YAMLCPP_DIR CMake variable +# to tell CMake where yaml-cpp is. + +# attempt to find static library first if this is set +if(YAMLCPP_STATIC_LIBRARY) + set(YAMLCPP_STATIC libyaml-cpp.a) +endif() + +# find the yaml-cpp include directory +find_path(YAMLCPP_INCLUDE_DIR yaml-cpp/yaml.h + PATH_SUFFIXES include + PATHS + ~/Library/Frameworks/yaml-cpp/include/ + /Library/Frameworks/yaml-cpp/include/ + /usr/local/include/ + /usr/include/ + /sw/yaml-cpp/ # Fink + /opt/local/yaml-cpp/ # DarwinPorts + /opt/csw/yaml-cpp/ # Blastwave + /opt/yaml-cpp/ + ${YAMLCPP_DIR}/include/) + +# find the yaml-cpp library +find_library(YAMLCPP_LIBRARY + NAMES ${YAMLCPP_STATIC} yaml-cpp + PATH_SUFFIXES lib64 lib + PATHS ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw + /opt/local + /opt/csw + /opt + ${YAMLCPP_DIR}/lib) + +# handle the QUIETLY and REQUIRED arguments and set YAMLCPP_FOUND to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(YAMLCPP DEFAULT_MSG YAMLCPP_INCLUDE_DIR YAMLCPP_LIBRARY) +mark_as_advanced(YAMLCPP_INCLUDE_DIR YAMLCPP_LIBRARY) diff --git a/cmake/application.cmake b/cmake/application.cmake new file mode 100644 index 0000000..9d509d8 --- /dev/null +++ b/cmake/application.cmake @@ -0,0 +1,38 @@ +# @brief Adds an executable target and performs related actions, +# such as verioning, binary dir and configuration. +# @note The default binary directory is PROJECT_BINARY_DIR, but can be overridden by specifying the ${PROJECT_NAME}_CURRENT_BINARY_DIR. +function(add_application) + +message( STATUS "${PROJECT_NAME} linking libraries : ${ARGN}") + +# Use PROJECT_BINARY_DIR by default, but override if necessary. +set(CURRENT_PROJECT_BINARY_DIR ${PROJECT_BINARY_DIR}) +if (${PROJECT_NAME}_CURRENT_BINARY_DIR) + set(CURRENT_PROJECT_BINARY_DIR ${${PROJECT_NAME}_CURRENT_BINARY_DIR}) +endif() +message(STATUS "CURRENT_PROJECT_BINARY_DIR : ${CURRENT_PROJECT_BINARY_DIR}") + +include_directories(${HSOA_VERSION_INCLUDE_DIR}) + +add_executable( ${PROJECT_NAME} + ${HSOA_VERSION_SRC_FILE} + ${SRC_LIST} +) + +target_link_libraries( ${PROJECT_NAME} + ${ARGN} +) + +set_target_properties( ${PROJECT_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + RUNTIME_OUTPUT_DIRECTORY ${CURRENT_PROJECT_BINARY_DIR}/bin +) + +# Copy the testconfig to the build dir, so the binaries are testable from the build dir. +# The created packages will not contain the testconfig but the production config instead. See installation.cmake. +if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/testconfig/) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/testconfig/" DESTINATION "${CURRENT_PROJECT_BINARY_DIR}/etc") +endif() + +endfunction() diff --git a/cmake/application.post_install.inc.cmake.in b/cmake/application.post_install.inc.cmake.in new file mode 100644 index 0000000..448a8d1 --- /dev/null +++ b/cmake/application.post_install.inc.cmake.in @@ -0,0 +1,14 @@ +# Make sure the mLogic group exists +MLOGIC_GROUP=mlogic +groupadd --force $MLOGIC_GROUP +# Create the user +appuserid=$(id -u @PROJECT_NAME@) +if [ ! $appuserid ] ; then + useradd --groups $MLOGIC_GROUP --no-create-home @PROJECT_NAME@ +fi +# Prevent direct login +passwd --lock @PROJECT_NAME@ +# Change group ownership of log folder +chgrp $MLOGIC_GROUP @PROJCOMP_LOG_INSTALL_DIR@ +# Change group ownership of run folder +chgrp $MLOGIC_GROUP @PROJCOMP_RUN_INSTALL_DIR@ diff --git a/cmake/application.post_uninstall.inc.cmake.in b/cmake/application.post_uninstall.inc.cmake.in new file mode 100644 index 0000000..8811e65 --- /dev/null +++ b/cmake/application.post_uninstall.inc.cmake.in @@ -0,0 +1,5 @@ +# Remove the user +appuserid=$(id -u @PROJECT_NAME@) +if [ $appuserid ] ; then + userdel --remove --force @PROJECT_NAME@ +fi diff --git a/cmake/artifacts.cmake b/cmake/artifacts.cmake new file mode 100644 index 0000000..6f541b8 --- /dev/null +++ b/cmake/artifacts.cmake @@ -0,0 +1,40 @@ +# @brief Tries to find the module with find_package, and uses CMAKE_INSTALL_PREFIX (see documentation). +# If the module can't be found, includes the directory by the specified module path. +# @param module The name of the module on which a dependency is added +# @param modulepath The relative path of the module wrt the current project +# @param moduleincludepath [optional] The relative path to the include directory +# @note Needs to be a macro because of the scope of the variables that are set by find_package. +macro(depend_module module modulepath) + message(STATUS "module : ${module}") + message(STATUS "modulepath : ${modulepath}") + + set ( optional_macro_args ${ARGN} ) + list ( LENGTH optional_macro_args num_optional_args ) + if ( ${num_optional_args} GREATER 0 ) + list ( GET optional_macro_args 0 moduleincludepath ) + else() + set ( moduleincludepath include ) + endif() + message(STATUS "moduleincludepath : ${moduleincludepath}") + + find_package(${module} CONFIG QUIET) + message( STATUS "${module}_FOUND: ${${module}_FOUND}" ) + + string( TOUPPER ${module} MODULE_NAME_UPPER ) + + # check if the constructed variable name exists and if so check also the content. + if ( NOT ${${module}_FOUND} ) + if ( NOT TARGET ${module} ) + message( STATUS "Adding subdirectory ${modulepath}/${module} as subdirectory ${CMAKE_CURRENT_LIST_DIR}/staging/${module}." ) + add_subdirectory(${modulepath}/${module} staging/${module}) + endif() + + set( ${MODULE_NAME_UPPER}_INCLUDE_DIR ${modulepath}/${module}/${moduleincludepath} CACHE STRING "${module} include path" FORCE ) + set( ${MODULE_NAME_UPPER}_LIB_DIR "${CMAKE_CURRENT_BINARY_DIR}" CACHE STRING "${module} library path" FORCE ) + set( ${MODULE_NAME_UPPER}_LIB_NAME "${module}" CACHE STRING "${module} library name" FORCE ) + endif() + + message( STATUS "${MODULE_NAME_UPPER}_INCLUDE_DIR: ${${MODULE_NAME_UPPER}_INCLUDE_DIR}" ) + message( STATUS "${MODULE_NAME_UPPER}_LIB_DIR: ${${MODULE_NAME_UPPER}_LIB_DIR}" ) + message( STATUS "${MODULE_NAME_UPPER}_LIB_NAME: ${${MODULE_NAME_UPPER}_LIB_NAME}" ) +endmacro() diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake new file mode 100644 index 0000000..b68d2ed --- /dev/null +++ b/cmake/compiler.cmake @@ -0,0 +1,223 @@ +include(CheckCXXCompilerFlag) + +set(PLATFORM_RELEASE "") +function(platformRelease) + set(PR, "") + execute_process(COMMAND /bin/bash -c "if [ -e /etc/redhat-release ]; then cat /etc/redhat-release | tr -d '\n'; fi" OUTPUT_VARIABLE PR) + message(STATUS "Platform is ${PR}") + set(PLATFORM_RELEASE "${PR}" PARENT_SCOPE) +endfunction(platformRelease) + +# The target architecture (-march, -mtune) is assumed to be the architecture that was used to build gcc. +# If cross-compilation is required, this should be specified as cmake arguments. + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshadow" ) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Woverloaded-virtual -Winit-self -Wuninitialized -Wunused -Wcast-qual" ) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-long-long" ) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast" ) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=return-type" ) + +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-noexcept-type" ) +endif() + +if(CMAKE_COMPILER_IS_GNUCC + AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 + AND NOT APPLE) + # Mac OSX doesn't seem to honour -isystem, so not for Mac. + # Also, GCC 4.8 complains about boost + MESSAGE(STATUS "Don't treat warnings as errors") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror" ) +endif() +if(BUILD_WITH_PROFILING) + message(STATUS "Profiling enabled") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg" ) +else() + message(STATUS "Profiling disabled") +endif() + +# Allow linking into a dynamic library +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) + +# Use RelWithDebInfo as default build type +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo") +endif() +message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") + +if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + set(NOASSERTS 1) # Disable asserts as well + # -O3 causes weird crashes on win32 so we force -O2 for release builds also for linux builds + string(REGEX REPLACE "-O3" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string(REGEX REPLACE "-O3" "-O2" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + if(MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") + endif() +else() + # Let Qt track the use of QSharedPointer + set(CMAKE_CXX_CFLAGS "${CMAKE_CXX_FLAGS} -DQT_SHAREDPOINTER_TRACK_POINTERS") + # If we're debugging, remove -O2 completely + string(REGEX REPLACE "-O2" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + string(REGEX REPLACE "-O2" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + string(REGEX REPLACE "-O2" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") + string(REGEX REPLACE "-O2" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") +endif() + +# Disable asserts with -DNOASSERTS=1 as a CMake command-line parameter +if(NOASSERTS) + add_definitions(-DQT_NO_DEBUG) + add_definitions(-DNDEBUG) + message(STATUS "Asserts have been disabled") +endif(NOASSERTS) + +if(APPLE) + # AVX instructions currently cause assembler errors, so disabling them + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w -mno-avx -mmacosx-version-min=10.7 -stdlib=libc++ ") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w -mno-avx -mmacosx-version-min=10.7") +endif(APPLE) + +if(UNIX) + if(BUILD_WITH_COVERAGE) + # Specifically enable coverate info, since ccache can't cache with + # profiling enabled, seriously hurting build-times in Debug-mode + message(STATUS "Generate coverage info") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage") + if (NOT TARGET RemoveGCDA) + add_custom_target(RemoveGCDA ALL + COMMAND find . -name "*.gcda" -delete + COMMENT "Removing gcda files") + endif(NOT TARGET RemoveGCDA) + + if(NOT NOASSERTS) + add_definitions(-DQT_SHAREDPOINTER_TRACK_POINTERS) + endif(NOT NOASSERTS) + else() + message(STATUS "Building without coverage info") + endif() +endif(UNIX) + +if(UNIX AND NOT APPLE) + # needed for making qwt happy + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lrt" ) +endif(UNIX AND NOT APPLE) + +if(MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mwindows" ) +endif(MINGW) + +CHECK_CXX_COMPILER_FLAG( -fstack-protector-all result ) +if(result) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-all") +endif(result) + +CHECK_CXX_COMPILER_FLAG( -fthreadsafe-statics THREADSAFE_RESULT) +if( NOT THREADSAFE_RESULT) + message(FATAL_ERROR, "Compiler does not support threadsafe statics variables in methods") +endif() +# We use static variables in method scope, they must be threadsafe, this is on by default since gcc 4 but this flag is a check +# If the compiler does not support this then build will fail. +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fthreadsafe-statics") + +if(CMAKE_COMPILER_IS_GNUCXX) + # Check on which architecture we are. ARM doesn't support mfpmath + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse" ) + endif() + + MESSAGE(STATUS "Checking the c++ Standard supported by the compiler" ) + # Check which version of c++ we can use. We drill down from c++17 to c96 + CHECK_CXX_COMPILER_FLAG( -std=c++17 cxxresult ) + if( NOT cxxresult ) + CHECK_CXX_COMPILER_FLAG( -std=c++14 cxxresult ) + if( NOT cxxresult ) + CHECK_CXX_COMPILER_FLAG( -std=c++11 cxxresult ) + if( NOT cxxresult ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + endif() + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") + endif() + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") + endif() + MESSAGE(STATUS "Compiling for ${CMAKE_CXX_FLAGS}") + + # -Wzero-as-null-pointer-constant is disabled for now, since the Qt 4.8.4 + # macro's produce a bucketload of these warnings. Might be useful later on. +# CHECK_CXX_COMPILER_FLAG( -Wzero-as-null-pointer-constant cxxresult ) +# if(cxxresult) +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wzero-as-null-pointer-constant") +# endif(cxxresult) + # Leave out deprecation warnings, mostly generated by CppUnit + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weffc++") + +endif(CMAKE_COMPILER_IS_GNUCXX) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + MESSAGE(STATUS "Enabling Clang flags") + # We enable all warnings and disable what we don't need + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything") + # Warns if items need to be re-aligned to 4-byte boundaries. Since we use + # booleans in our code, these warnings are all over the place. If + # memory-usage becomes an issue, we can pack all booleans together using + # this warning. However, it's currently not practical to eliminate them all. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-padded") + # We use a lot of static objects, which get cleaned up at exit by their destructors. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-exit-time-destructors") + # Static objects tend to have global constructors + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-global-constructors") + # We mix int / unsigned int a lot, so disabled it for the moment + #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-sign-conversion") + # Warning caused by the Qt translations + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-prototypes") + # We standardize on C++11, so don't bother us with C++98 + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat -Wno-c++98-compat-pedantic") + # We explicitly want to use the switch(){default: break;} construction + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-switch-enum") + # Q_OBJECT does an "int i = i;"... + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-self-assign") + # Compile for C++11 + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + # Don't complain about unused arguments, since this also triggers warnings + # about include-directories that are unused... + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments") + # Don't complain about fall-throughs in switch/case statements + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-implicit-fallthrough") + # Since "Q_ASSERT(false)" creates a not of noise, disable it when asserts are active + if(NOT NOASSERTS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unreachable-code") + endif(NOT NOASSERTS) + # There is no way to avoid this warning right now, so we disable it + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-weak-template-vtables") + # Warning caused by Qt resource files, so disabling it + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-variable-declarations") + # WORKAROUND: Somehow Qt forgets that Clang is a C++11 compiler + ADD_DEFINITIONS("-DQ_COMPILER_INITIALIZER_LISTS") + # Clang 3.3 doesn't know Doxygens' "\test" and "\retval" tags, so disabling + # check for unknown tags for now + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-documentation-unknown-command") + + if(CMAKE_CXX_COMPILER_VERSION MATCHES "3\\.4\\.2") + message(STATUS "compiler is clang version 3.4.2") + platformRelease() + if("${PLATFORM_RELEASE}" MATCHES "CentOS Linux release 7\\.2\\..+") + # Override the gnu minor version only for this specific combination of platform and clang version + set(CMAKE_CXX_FLAGS "-U__GNUC_MINOR__ -D__GNUC_MINOR__=3 ${CMAKE_CXX_FLAGS}") + message(STATUS "Overriding clang gnu minor version to 3 so that several libstd c++11 features like tuple can be used") + endif("${PLATFORM_RELEASE}" MATCHES "CentOS Linux release 7\\.2\\..+") + endif(CMAKE_CXX_COMPILER_VERSION MATCHES "3\\.4\\.2") +endif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + +if(APPLE) + set(WHOLE_ARCHIVE_ON "-all_load") + set(WHOLE_ARCHIVE_OFF "") +else() + set(WHOLE_ARCHIVE_ON "-Wl,-whole-archive") + set(WHOLE_ARCHIVE_OFF "-Wl,-no-whole-archive") +endif() diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in new file mode 100644 index 0000000..161ab72 --- /dev/null +++ b/cmake/config.cmake.in @@ -0,0 +1,22 @@ +@PACKAGE_INIT@ + +string( TOUPPER @PROJECT_NAME@ PROJECT_NAME_UPPER ) + +string( CONCAT MY_PACKAGE_INCLUDE_DIR ${PROJECT_NAME_UPPER} "_INCLUDE_DIR" ) +set_and_check(${MY_PACKAGE_INCLUDE_DIR} "@PACKAGE_PROJCOMP_INCLUDE_INSTALL_DIR@") +message( STATUS "${MY_PACKAGE_INCLUDE_DIR}: ${${MY_PACKAGE_INCLUDE_DIR}}" ) + +string( CONCAT MY_PACKAGE_LIB_DIR ${PROJECT_NAME_UPPER} "_LIB_DIR" ) +set_and_check(${MY_PACKAGE_LIB_DIR} "@PACKAGE_PROJCOMP_LIB_INSTALL_DIR@") +message( STATUS "${MY_PACKAGE_LIB_DIR}: ${${MY_PACKAGE_LIB_DIR}}" ) + +string( CONCAT MY_PACKAGE_CMAKE_DIR ${PROJECT_NAME_UPPER} "_CMAKE_DIR" ) +set_and_check(${MY_PACKAGE_CMAKE_DIR} "@PACKAGE_PROJCOMP_CMAKE_INSTALL_DIR@") +message( STATUS "${MY_PACKAGE_CMAKE_DIR}: ${${MY_PACKAGE_CMAKE_DIR}}" ) + +string( CONCAT MY_PACKAGE_LIB_NAME ${PROJECT_NAME_UPPER} "_LIB_NAME" ) +set(${MY_PACKAGE_LIB_NAME} "@PROJECT_NAME@") +message( STATUS "${MY_PACKAGE_LIB_NAME}: ${${MY_PACKAGE_LIB_NAME}}" ) + +check_required_components("@PROJECT_NAME@") +#message( STATUS "@PROJECT_NAME@_FOUND: ${${@PROJECT_NAME@}_FOUND}" ) diff --git a/cmake/installation.cmake b/cmake/installation.cmake new file mode 100644 index 0000000..ce38a18 --- /dev/null +++ b/cmake/installation.cmake @@ -0,0 +1,192 @@ +set( INSTALLATION_CURRENT_CMAKE_DIR ${CMAKE_CURRENT_LIST_DIR} ) + +# Layout +set(USR_LOCAL_DIR "/usr/local" CACHE STRING "" FORCE) +set(LD_SO_CONF_D_DIR "/etc/ld.so.conf.d" CACHE STRING "" FORCE) +set(SYSTEMD_CONFIG_DIR "/etc/systemd/system" CACHE STRING "" FORCE) +set(VAR_LOG_DIR "/var/log" CACHE STRING "" FORCE) +set(VAR_RUN_DIR "/var/run" CACHE STRING "" FORCE) +set(PROJCOMP_LOG_INSTALL_DIR "${VAR_LOG_DIR}/${PROJECT_NAME}") +set(PROJCOMP_RUN_INSTALL_DIR "${VAR_RUN_DIR}/${PROJECT_NAME}") +set(PROJCOMP_BIN_INSTALL_DIR "${PROJECT_NAME}/bin") +set(PROJCOMP_LIB_INSTALL_DIR "lib") +set(PROJCOMP_INCLUDE_INSTALL_DIR "${PROJECT_NAME}/include") +set(PROJCOMP_ETC_INSTALL_DIR "${PROJECT_NAME}/etc") +set(PROJCOMP_CMAKE_INSTALL_DIR "${PROJECT_NAME}/cmake") +set(PROJCOMP_COMPONENT_NAME "${REPOSITORY_PACKAGE_NAME}-${PROJECT_NAME}") +set(PROJCOMP_COMPONENT_NAME_DEVEL "${PROJCOMP_COMPONENT_NAME}-devel") + +# @brief Installs a library component and performs related actions. +function(install_component) + +set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") + +# Configuration +string(TOLOWER ${PROJECT_NAME} MODULE_NAME_LOWER) +set(PROJECT_CONFIG "${GENERATED_DIR}/${MODULE_NAME_LOWER}-config.cmake") + +# Generate the cmake config file +include(CMakePackageConfigHelpers) +configure_package_config_file( + "${INSTALLATION_CURRENT_CMAKE_DIR}/config.cmake.in" + "${PROJECT_CONFIG}" + INSTALL_DESTINATION "${PROJCOMP_CMAKE_INSTALL_DIR}" + PATH_VARS PROJCOMP_INCLUDE_INSTALL_DIR PROJCOMP_LIB_INSTALL_DIR PROJCOMP_CMAKE_INSTALL_DIR +) + +# Install license file +if (EXISTS "${INSTALLATION_CURRENT_CMAKE_DIR}/COPYING.OSDEV") + install( + FILES "${INSTALLATION_CURRENT_CMAKE_DIR}/COPYING.OSDEV" + DESTINATION ${PROJCOMP_LIB_INSTALL_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME}" + ) +endif() + +# Install targets +install( + TARGETS ${PROJECT_NAME} + DESTINATION ${PROJCOMP_LIB_INSTALL_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME}" +) + +# Configure and install dynamic linker runtime bindings (ld.so.conf.d file) +configure_file( + "${INSTALLATION_CURRENT_CMAKE_DIR}/ld.so.conf.d.cmake.in" + "${GENERATED_DIR}/${PROJECT_NAME}.conf" +) +install( + FILES "${GENERATED_DIR}/${PROJECT_NAME}.conf" + DESTINATION ${LD_SO_CONF_D_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME}" +) + +# Configure and set cpack post install script +configure_file( + "${INSTALLATION_CURRENT_CMAKE_DIR}/library.post_install.inc.cmake.in" + "${GENERATED_DIR}/library.post_install.inc" +) +# Set the per-component post install script file. +set(CPACK_RPM_${PROJCOMP_COMPONENT_NAME}_POST_INSTALL_SCRIPT_FILE "${GENERATED_DIR}/library.post_install.inc" CACHE STRING "${PROJECT_NAME} post_install script" FORCE) + +# Headers +# At the moment, we anticipate the headers to be located according to either 1) or 2). + +# 1) Apply glob style pattern for those projects that have the header files in the root folder of the repository (such as opcua_model). +file (GLOB PACKAGE_HEADERS "${CMAKE_CURRENT_LIST_DIR}/*.h*") +install( + FILES ${PACKAGE_HEADERS} + DESTINATION ${PROJCOMP_INCLUDE_INSTALL_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME_DEVEL}" +) +# 2) Copy the include dir for those projects that have the header files in include/mlogic (such as opc_utils). +if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/include/mlogic) + install( + DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/mlogic + DESTINATION ${PROJCOMP_INCLUDE_INSTALL_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME_DEVEL}" + ) +endif() + +# Install cmake project config +install( + FILES ${PROJECT_CONFIG} + DESTINATION ${PROJCOMP_CMAKE_INSTALL_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME_DEVEL}" +) + +# The -devel package depends on the runtime package +set(CPACK_RPM_${PROJCOMP_COMPONENT_NAME_DEVEL}_PACKAGE_REQUIRES "${PROJCOMP_COMPONENT_NAME} = ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CURRENT_PROJECT_VERSION_RELEASENR}" CACHE STRING "${PROJCOMP_COMPONENT_NAME_DEVEL} dependency" FORCE ) +# Clear the dependencies for the runtime package, as cpack erroneously added unexpected dependencies (hints towards the previously built package) +set(CPACK_RPM_${PROJCOMP_COMPONENT_NAME}_PACKAGE_REQUIRES "" CACHE STRING "${PROJCOMP_COMPONENT_NAME} dependencies" FORCE ) + +endfunction() + +# @brief Installs the executable binary and related items +# (such as systemd service files, log folder, post [un]install scripts) +# @param INSTALL_SYSTEMD_SERVICE [optional] Boolean to indicate whether the systemd unit files must be installed. Optional. The default is ON. +function(install_application) + +set ( optional_macro_args ${ARGN} ) +list ( LENGTH optional_macro_args num_optional_args ) +if ( ${num_optional_args} GREATER 0 ) + list ( GET optional_macro_args 0 INSTALL_SYSTEMD_SERVICE ) +endif() + +if(NOT DEFINED INSTALL_SYSTEMD_SERVICE) + set(INSTALL_SYSTEMD_SERVICE "ON") +endif() + +message(STATUS "INSTALL_SYSTEMD_SERVICE: ${INSTALL_SYSTEMD_SERVICE}") + +set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") + +# Install license file +if (EXISTS "${INSTALLATION_CURRENT_CMAKE_DIR}/COPYING.OSDEV") + install( + FILES "${INSTALLATION_CURRENT_CMAKE_DIR}/COPYING.OSDEV" + DESTINATION ${PROJCOMP_BIN_INSTALL_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME}" + ) +endif() + +# Install binary file +install( + TARGETS ${PROJECT_NAME} + DESTINATION ${PROJCOMP_BIN_INSTALL_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME}" +) + +# Install configuration +if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/config/) + install( + DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/config/ + DESTINATION ${PROJCOMP_ETC_INSTALL_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME}" + ) +endif() + +if (INSTALL_SYSTEMD_SERVICE STREQUAL "ON") + # Configure and install systemd unit file + configure_file( + "${INSTALLATION_CURRENT_CMAKE_DIR}/service.cmake.in" + "${GENERATED_DIR}/${PROJECT_NAME}.service" + ) + install( + FILES "${GENERATED_DIR}/${PROJECT_NAME}.service" + DESTINATION ${SYSTEMD_CONFIG_DIR} + COMPONENT "${PROJCOMP_COMPONENT_NAME}" + ) +endif() + +# Install log folder +install( + DIRECTORY + DESTINATION ${PROJCOMP_LOG_INSTALL_DIR} + DIRECTORY_PERMISSIONS + OWNER_WRITE OWNER_READ OWNER_EXECUTE + GROUP_WRITE GROUP_READ GROUP_EXECUTE SETGID + # No permissions for WORLD + COMPONENT "${PROJCOMP_COMPONENT_NAME}" +) + +# Install run folder +install( + DIRECTORY + DESTINATION ${PROJCOMP_RUN_INSTALL_DIR} + DIRECTORY_PERMISSIONS + OWNER_WRITE OWNER_READ OWNER_EXECUTE + GROUP_WRITE GROUP_READ GROUP_EXECUTE + # No permissions for WORLD + COMPONENT "${PROJCOMP_COMPONENT_NAME}" +) + +# Configure and set cpack post install script +configure_file( + "${INSTALLATION_CURRENT_CMAKE_DIR}/application.post_install.inc.cmake.in" + "${GENERATED_DIR}/application.post_install.inc" +) +# Set the per-component post install script file. +set(CPACK_RPM_${PROJCOMP_COMPONENT_NAME}_POST_INSTALL_SCRIPT_FILE "${GENERATED_DIR}/application.post_install.inc" CACHE STRING "${PROJECT_NAME} post_install script" FORCE) + +endfunction() diff --git a/cmake/ld.so.conf.d.cmake.in b/cmake/ld.so.conf.d.cmake.in new file mode 100644 index 0000000..3aee369 --- /dev/null +++ b/cmake/ld.so.conf.d.cmake.in @@ -0,0 +1 @@ +@CMAKE_INSTALL_PREFIX@/@PROJCOMP_LIB_INSTALL_DIR@/ diff --git a/cmake/library.cmake b/cmake/library.cmake new file mode 100644 index 0000000..627dcb7 --- /dev/null +++ b/cmake/library.cmake @@ -0,0 +1,41 @@ +# @brief Adds a single shared or static library target and performs related actions, +# such as target properties and some packaging variables. +# @note The default binary directory is CMAKE_BINARY_DIR, but can be overridden by specifying the ${PROJECT_NAME}_CURRENT_BINARY_DIR. +function(add_libraries) + +message( STATUS "${PROJECT_NAME} linking libraries : ${ARGN}") + +# Use CMAKE_BINARY_DIR by default, but override if necessary. +set(CURRENT_PROJECT_BINARY_DIR ${CMAKE_BINARY_DIR}) +if (${PROJECT_NAME}_CURRENT_BINARY_DIR) + set(CURRENT_PROJECT_BINARY_DIR ${${PROJECT_NAME}_CURRENT_BINARY_DIR}) +endif() +message(STATUS "CURRENT_PROJECT_BINARY_DIR : ${CURRENT_PROJECT_BINARY_DIR}") + +if( ${BUILD_STATIC} ) + if(${BUILD_STATIC} STREQUAL "ON") + set(SHARED_OR_STATIC "STATIC") + else() + set(SHARED_OR_STATIC "SHARED") + endif() +else() + set(SHARED_OR_STATIC "SHARED") +endif() + +add_library( ${PROJECT_NAME} ${SHARED_OR_STATIC} + ${SRC_LIST} +) + +target_link_libraries( ${PROJECT_NAME} + ${ARGN} +) + +set_target_properties( ${PROJECT_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + LIBRARY_OUTPUT_DIRECTORY ${CURRENT_PROJECT_BINARY_DIR}/lib + ARCHIVE_OUTPUT_DIRECTORY ${CURRENT_PROJECT_BINARY_DIR}/lib +) + +endfunction() diff --git a/cmake/library.post_install.inc.cmake.in b/cmake/library.post_install.inc.cmake.in new file mode 100644 index 0000000..6bdc1cc --- /dev/null +++ b/cmake/library.post_install.inc.cmake.in @@ -0,0 +1,16 @@ +# Update the dynamic linker runtime bindings. +VAR1="/sbin/ldconfig" +VAR2=`which ldconfig` + +if [ -e $VAR1 ]; then + $VAR1 +elif [ ! -z $VAR2 ]; then + $VAR2 +else + VAR3=`find / -name ldconfig -type f` + if [ -e $VAR3 ]; then + $VAR3 + else + echo "ldconfig not found!!!" + fi +fi diff --git a/cmake/packaging.cmake b/cmake/packaging.cmake new file mode 100644 index 0000000..cfef30e --- /dev/null +++ b/cmake/packaging.cmake @@ -0,0 +1,81 @@ +set( PACKAGING_CURRENT_CMAKE_DIR ${CMAKE_CURRENT_LIST_DIR} ) + +# @brief Creates component packages. Can be executed by calling "make package". +function(package_component) + +# Determine build architecture +execute_process( + COMMAND uname -m + COMMAND tr -d '\n' + OUTPUT_VARIABLE ARCHITECTURE +) + +# Set package name +# Known issue: Due to the way cpack works (only evaluated once), the PROJECT_NAME will be the last package to +# call this function. We worked around this by including packaging.cmake only from 1 location in the repository. +# However, in a superbuild (i.e. from an mlogic parent directory building all repositories at once), +# this will yield an erroneous package name (usually datacollector, as that is the last project to be evaluated). +# In order to prevent dynamic package names caused by different build directories, +# The package name is set to a fixed value. The actual name of the rpm and package will be determined by +# other variable values such as the component name and version, leading to uniquely identifiable packages. +set(CPACK_PACKAGE_NAME osdev) + +# Set package version numbers +set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") +# The release number is not part of CPACK_PACKAGE_VERSION and is dealt with seperately (see below) +set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +message( STATUS "Packaging ${CPACK_PACKAGE_NAME} ${CPACK_PACKAGE_VERSION} for ${ARCHITECTURE}.") + +set(CPACK_PACKAGE_RELEASE "1") +if (CURRENT_PROJECT_VERSION_RELEASENR) + set(CPACK_PACKAGE_RELEASE ${CURRENT_PROJECT_VERSION_RELEASENR}) +endif() + +# Build CPack driven installer packages +include(InstallRequiredSystemLibraries) + +# This doesn't seem to work, only the specific archive variables (_RPM_, _DEB_) seem to work +#set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) +set(CPACK_COMPONENTS_IGNORE_GROUPS 1) +# Enable the line below if the repository being built has only 1 target that's installed +# (No longer necessary, as we now have at least 2 components: runtime and development components.) +#set(CPACK_COMPONENTS_ALL ${CPACK_PACKAGE_NAME} ${CPACK_PACKAGE_NAME}-devel) + +# Enable DESTDIR and copy CMAKE_INSTALL_PREFIX, which requires disabling rpm relocatability +set(CPACK_SET_DESTDIR 1) +set(CPACK_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) +set(CPACK_PACKAGE_RELOCATABLE OFF) + +# Set package metadata +if (EXISTS "${PACKAGING_CURRENT_CMAKE_DIR}/../../../COPYING.OSDEV") + set(CPACK_RESOURCE_FILE_LICENSE "${PACKAGING_CURRENT_CMAKE_DIR}/../../../COPYING.OSDEV") +endif() +if (EXISTS "${PACKAGING_CURRENT_CMAKE_DIR}/../../../README.md") + set(CPACK_RESOURCE_FILE_README "${PACKAGING_CURRENT_CMAKE_DIR}/../../../README.md") +endif() +set(CPACK_SYSTEM_NAME "${ARCHITECTURE}") +set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.${CURRENT_PROJECT_VERSION_LETTER}-${CPACK_PACKAGE_RELEASE}.${ARCHITECTURE}") + +# RPM specific fields +set(CPACK_RPM_PACKAGE_ARCHITECTURE "${ARCHITECTURE}") +set(CPACK_RPM_COMPONENT_INSTALL ON) +set(CPACK_RPM_PACKAGE_GROUP "${CPACK_PACKAGE_NAME}-${REPOSITORY_PACKAGE_NAME}") +# Unfortunately, CPACK_RPM_PACKAGE_RELEASE isn't inherited from CPACK_PACKAGE_RELEASE automatically. +set(CPACK_RPM_PACKAGE_RELEASE ${CPACK_PACKAGE_RELEASE}) +set(CPACK_RPM_PACKAGE_VENDOR ${CURRENT_PROJECT_MANUFACTURER_CODE}) +set(CPACK_RPM_PACKAGE_SUMMARY "${CPACK_PACKAGE_NAME} package") +set(CPACK_RPM_PACKAGE_DESCRIPTION "${CPACK_RPM_PACKAGE_SUMMARY}") +set(CPACK_RPM_PACKAGE_LICENSE "${CURRENT_PROJECT_MANUFACTURER_CODE}") + +# Select CPack generators +set(CPACK_GENERATOR "RPM") + +# Exclude certain system directories from the rpm +set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION /usr;/etc;/etc/systemd;/var;${USR_LOCAL_DIR};${LD_SO_CONF_D_DIR};${SYSTEMD_CONFIG_DIR};${VAR_LOG_DIR};${VAR_RUN_DIR}) + +# This line should come last +include(CPack) + +endfunction(package_component) diff --git a/cmake/projectheader.cmake b/cmake/projectheader.cmake new file mode 100644 index 0000000..d3d0340 --- /dev/null +++ b/cmake/projectheader.cmake @@ -0,0 +1,58 @@ +# @brief Defines the project name, version and binary dir. +# @param CURRENT_PROJECT_NAME The name of the project to define. +# @param CURRENT_PROJECT_BINARY_DIR [optional] Override for the default project binary dir (PROJECT_BINARY_DIR for executables, CMAKE_BINARY_DIR for libraries). +macro ( project_header CURRENT_PROJECT_NAME ) + +# Determine the version and fill the following variables : +# SOVERSION : The tag from the git-repository. Not necessarily a number-only string. +find_package(Git QUIET) + +if(GIT_FOUND) + if( EXISTS "${PROJECT_SOURCE_DIR}/.git") + execute_process(COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 --tags + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE CURRENT_PROJECT_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE GIT_SUBMOD_RESULT) + + # Exit code will be 128 if no tags are present + if( ${GIT_SUBMOD_RESULT} EQUAL 128 ) + set(CURRENT_PROJECT_VERSION "0.0.1") + endif() + + + + message( STATUS "================================================================" ) + message( STATUS "Found the following tag : ${CURRENT_PROJECT_VERSION}") + message( STATUS "================================================================" ) + else() + message( STATUS ".git directory does not exists..") + message( STATUS "Project directory : ${PROJECT_SOURCE_DIR}") + endif() +else() + message( STATUS "git-command not found....") + message( FATAL "Unable to determine the version of the software.") +endif() + + +message( STATUS "" ) +message( STATUS "================================================================" ) +message( STATUS "Creating Makefile of ${CURRENT_PROJECT_NAME}" ) +message( STATUS "================================================================" ) +message( STATUS "CURRENT_PROJECT_VERSION: ${CURRENT_PROJECT_VERSION}" ) + +set(SOVERSION "${CURRENT_PROJECT_VERSION}") +set(VERSION "${CURRENT_PROJECT_VERSION}") + +project(${CURRENT_PROJECT_NAME} VERSION ${CURRENT_PROJECT_VERSION}) +set_property(GLOBAL PROPERTY GLOBAL_DEPENDS_NO_CYCLES 1) + +set ( optional_macro_args ${ARGN} ) +list ( LENGTH optional_macro_args num_optional_args ) +if ( ${num_optional_args} GREATER 0 ) + # Set project binary dir override to the specified value + list ( GET optional_macro_args 0 CURRENT_PROJECT_BINARY_DIR ) + set ( ${PROJECT_NAME}_CURRENT_BINARY_DIR ${CURRENT_PROJECT_BINARY_DIR} CACHE STRING "${PROJECT_NAME} binary dir" FORCE ) +endif() + +endmacro() diff --git a/cmake/qtmoc.cmake b/cmake/qtmoc.cmake new file mode 100644 index 0000000..2d5a1d7 --- /dev/null +++ b/cmake/qtmoc.cmake @@ -0,0 +1,33 @@ +# @brief Creates the qt5 mocs for the specified header files. +# @param SRC_LIST The current source list of the project, to which to add the created moc files. +# @param MOC_LIST The list of header files for which to create qt5 mocs. +macro(create_mocs SRC_LIST MOC_LIST) + +message( STATUS "${PROJECT_NAME} Creating mocs for: ${ARGN}") + +set( MOCABLE_LIST + ${ARGN} +) + +# Empty the MOC_LIST variable +set( ${MOC_LIST} +) + +# Create the MOC_LIST +QT6_WRAP_CPP( ${MOC_LIST} ${MOCABLE_LIST} ) + +# Append SRC_LIST with MOC_LIST +list ( APPEND SRC_LIST + ${${MOC_LIST}} +) + +message( STATUS "${PROJECT_NAME} MOC_LIST: ${${MOC_LIST}}") +message( STATUS "${PROJECT_NAME} SRC_LIST: ${${SRC_LIST}}") + +set_source_files_properties( + ${${MOC_LIST}} + PROPERTIES + COMPILE_FLAGS -Wno-undefined-reinterpret-cast +) + +endmacro() diff --git a/cmake/qtuic.cmake b/cmake/qtuic.cmake new file mode 100644 index 0000000..8e3bf21 --- /dev/null +++ b/cmake/qtuic.cmake @@ -0,0 +1,39 @@ +# @brief Creates the qt5 ui_ headers for the specified designer files. +# @param SRC_LIST The current source list of the project, to which to add the created moc files. +# @param UIC_LIST The list of designer files for which to create qt5 headers. +macro(create_ui SRC_LIST UIC_LIST) + +message( STATUS "${PROJECT_NAME} Creating headers for: ${ARGN}") + +set( UICABLE_LIST + ${ARGN} +) + +# Empty the UIC_LIST variable +set( ${UIC_LIST} +) + +# Create the UIC_LIST +QT5_WRAP_UI( ${UIC_LIST} ${UICABLE_LIST} ) + +# Append SRC_LIST with UIC_LIST +list ( APPEND SRC_LIST + ${${UIC_LIST}} +) + +# Avoid warnings by including a generated header file. +include_directories( ${SYSTEMORNOT} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR} +) + +message( STATUS "${PROJECT_NAME} UIC_LIST: ${${UIC_LIST}}") +message( STATUS "${PROJECT_NAME} SRC_LIST: ${${SRC_LIST}}") + +set_source_files_properties( + ${${UIC_LIST}} + PROPERTIES + COMPILE_FLAGS -Wno-undefined-reinterpret-cast +) + +endmacro() diff --git a/cmake/service.cmake.in b/cmake/service.cmake.in new file mode 100644 index 0000000..1193cc8 --- /dev/null +++ b/cmake/service.cmake.in @@ -0,0 +1,11 @@ +[Unit] +Description=Mlogic @PROJECT_NAME@ +After=network.target + +[Service] +WorkingDirectory=@CMAKE_INSTALL_PREFIX@/@PROJECT_NAME@/ +ExecStart=@CMAKE_INSTALL_PREFIX@/@PROJCOMP_BIN_INSTALL_DIR@/@PROJECT_NAME@ +User=@PROJECT_NAME@ + +[Install] +WantedBy=multi-user.target diff --git a/cmake/sync.sh b/cmake/sync.sh new file mode 100755 index 0000000..a921bb0 --- /dev/null +++ b/cmake/sync.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# $1: cmake-file-filename +function sync-file() +{ + echo ---------- Syncing $1 started + + cp -f ../../mlogic_support/cmake/$1 $1 + if [ $? -ne 0 ] ; then + echo ---------- Syncing $1 failed + return 1 + fi + + echo ---------- Syncing $1 finished +} + +# We don't copy installation.cmake, because the includes files are in the src folder. +# datacollector is different in this respect from the other repositories. +MLOGIC_CMAKE_FILES="artifacts.cmake +compiler.cmake +config.cmake.in +FindGMock.cmake +library.cmake +packaging.cmake +projectheader.cmake +qtmoc.cmake" + +# Process the cmake files +cd $(dirname $(readlink -f $0)) || exit 1 +for cmake_file in ${MLOGIC_CMAKE_FILES} +do + sync-file ${cmake_file} +done diff --git a/cmake/targetprops.cmake b/cmake/targetprops.cmake new file mode 100644 index 0000000..2ac88c9 --- /dev/null +++ b/cmake/targetprops.cmake @@ -0,0 +1,7 @@ +set_target_properties( ${PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/archive + LINKER_LANGUAGE CXX +) + diff --git a/include/daemonbase.h b/include/daemonbase.h new file mode 100644 index 0000000..e019f3d --- /dev/null +++ b/include/daemonbase.h @@ -0,0 +1,323 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ****************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "daemonlog.h" +#include "daemonconfig.h" + +namespace osdev::components::daemon +{ +class DaemonBase +{ +private: + static DaemonBase *instance; + +public: + /** + * Construct a new daemon process. + * @note: only one daemon per application is possible. + * @param name: name of daemon process + * @param cwd: daemon current working directory, root "/" directory by default. + * @param update_duration: duration to sleep before waking up the on_update() callback every time, deafult 10 seconds. + */ + DaemonBase(const std::string &name, + const std::string &cwd = "/", + const std::chrono::high_resolution_clock::duration &update_duration = std::chrono::seconds(10)) + : m_name(name) + , m_cwd(cwd) + , m_update_duration(update_duration) + , m_is_running(false) + , m_exit_code(EXIT_SUCCESS) + { + if (instance) + { + daemonlog::error("Only one daemon instance is possible."); + std::exit(EXIT_FAILURE); + } + instance = this; + } + + DaemonBase() + : m_name("") + , m_cwd("/") + , m_update_duration(std::chrono::seconds(10)) + , m_is_running(false) + { + if(instance) + { + daemonlog::error("Only one daemon instance is possible."); + std::exit(EXIT_FAILURE); + } + instance = this; + } + + void run(int argc, char *argv[]) + { + if (m_is_running.load()) + { + daemonlog::error("Daemon '" + m_name + "' is already running."); + return; + } + + // Get config file path from cmd args passed by ExecStart=/usr/bin/my_daemon --config /etc/my_daemon/my_daemon.conf + // since we need it for a reload./ + for (std::int32_t i = 0; i < argc; i++) + { + if (!std::strcmp(argv[i], "--config")) + { + if (i + 1 < argc) + { + m_config_file = argv[i + 1]; + } + else + { + daemonlog::error("Missing config file. Did ytou forget to specify a config file in your .serve file's ExecStart ?"); + } + break; + } + } + + // daemonize this program by forking the parent process. + daemonize(); + + // Mark as running (better to have it before on_start() as a user may call stop() inside on_start()). + m_is_running = true; + on_start(daemonconfig::from_file(m_config_file)); + while (m_is_running.load()) + { + on_update(); + + // On long sleeps, if we want to exit we need a condition_variable to wake up the thread from sleep to carry on exiting. + std::unique_lock lock(m_mutex); + m_update_cv.wait_for(lock, m_update_duration, [this]() + { + return !m_is_running.load(); + }); + } + on_stop(); + } + + void stop(std::int32_t code = EXIT_SUCCESS) + { + m_exit_code = code; + m_is_running.store(false); + m_update_cv.notify_all(); + } + + virtual ~DaemonBase() + { + daemonlog::shutdown(); + // Terminate the child process when the daemon completes (loop stopped) + // @note that calling std::exit() inside the run function will not call DTor, + std::exit(m_exit_code); + } + + // Getters & Setters + void set_update_duration(const std::chrono::high_resolution_clock::duration &duration) noexcept + { m_update_duration = duration; } + + const std::chrono::high_resolution_clock::duration& get_update_duration() const noexcept + { return m_update_duration; } + + void set_name(const std::string &daemon_name) noexcept + { m_name = daemon_name; } + + const std::string& get_name() const noexcept + { return m_name; } + + void set_cwd(const std::string ¤t_working_dir) noexcept + { + // Change the current working directory to a directory guaranteed to exist, provided by the user. + if (chdir(current_working_dir.c_str()) < 0) + { + daemonlog::error("Could not change current working directory to'" + + current_working_dir + "': " + + std::string(std::strerror(errno))); + return; + } + m_cwd = current_working_dir; + } + + const std::string& get_cwd() const noexcept { return m_cwd; } + + pid_t get_pid() const noexcept { return m_pid; } + pid_t get_sid() const noexcept { return m_sid; } + +protected: // Callbacks + /** + * @brief Called once on daemon starts + * @scenarios: + * - when system starts + * - when you run `$ systemctl start your_daemon` manually + * @param cfg: Installed daemon config file + * Initialize your code here... + */ + virtual void on_start(const daemonconfig &cfg) = 0; + + /** + * @brief Called every DURATION which was set by set_update_duration(DURATION). + * Update your code here... + */ + virtual void on_update() = 0; + + /** + * @brief Called once before daemon is about to exit. + * @scenarios: + * - when you call stop(ewxit_code) + * - when you run `$ systemctl stop your_daemon` manually + * - when the system kills your daemon for some reason + * Cleanup your code here... + */ + virtual void on_stop() = 0; + + /** + * @brief Called once when daemon's config or service files are updated. + * @scenarios: + * - when you run `$systemctl daemon-reload` after you have changed your .conf or .service files + * (after reinstalling your daemon with `$ sudo make install` for example) + * Reinitialize your code here... + */ + virtual void on_reload(const daemonconfig &cfg) = 0; + +private: + static void signal_handler(std::int32_t sig) + { + daemonlog::info("Signal " + std::to_string(sig) + " received."); + + switch(sig) + { + // daemon.service handler : ExecStop=/bin/kill -s SIGTERM $MAINPID + // When daemon is stopped, system sends SIGTERM first. + // If daemon didn't respond during 90 seconds, it will send a SIGKILL signal + case SIGTERM: + case SIGKILL: + { + instance->stop(); + break; + } + // daemon.service handler : ExecReload=/bin/kill -S SIGHUB $MAINPID + // When a daemon is reloaded due updates in .service or .conf, system sends SIGHUP signal. + case SIGHUP: + { + instance->on_reload(daemonconfig::from_file(instance->m_config_file)); + break; + } + default: + { + break; + } + } + } + + /** + * @brief Daemonize this program + * @note: It is also possible to use glibc function daemon() + * at this point, but it is useful to customize your daemon. + * Like for example handle signals, set working directory... + */ + void daemonize() + { + // Fork off the parent process (https://linux.die.net/man/3/fork) + m_pid = fork(); + // Success: The parent process continues with a PID > 0 + if (m_pid > 0) + { + std::exit(EXIT_SUCCESS); + } + else if (m_pid < 0) + { + // An error occurred. A process ID lower than 0 indicates a failure in either process + std::exit(EXIT_FAILURE); + } + // The parent process has now terminated, and the forked child process will continue + // (the pid of the child process was 0) + + // Since the child process is a daemon, the unask needs to be set so files and logs can be written + umask(0); + + // Initialize syslog for this daemon, here it's a good place to do so. + daemonlog::init(m_name); + + // On success: The child process becomes session leader. Generate a session ID for the child process. + m_sid = setsid(); + if (m_sid < 0) + { + daemonlog::error("Could not set SID to child process: " + std::string(std::strerror(errno))); + std::exit(EXIT_FAILURE); + } + + // Ignore the Child terminated or stopped signal. + std::signal(SIGCHLD, SIG_IGN); + + // Set signal handlers to detect daemon interrupt, restart.. + std::signal(SIGHUP, signal_handler); + + // When a sudo systemctl stop my_daemon is ran, by default, a SIGTERM is sent, + // followed by 90 seconds of waiting followed by a SIGKILL + std::signal(SIGTERM, signal_handler); + std::signal(SIGKILL, signal_handler); + + // Change the current working directory to a directory guaranteed to exist, procided by the user + if (chdir(m_cwd.c_str()) < 0) + { + daemonlog::error("Could not change current working directory to `" + m_cwd + "': " + std::string(std::strerror(errno))); + std::exit(EXIT_FAILURE); + } + + // A daemon cannot use the terminal, so close standard file descriptors for security reasons + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + + // Member variables + pid_t m_pid; + pid_t m_sid; + std::string m_config_file; + std::string m_name; + std::string m_cwd; + std::chrono::high_resolution_clock::duration m_update_duration; + std::atomic m_is_running; + std::condition_variable m_update_cv; + std::mutex m_mutex; + std::int32_t m_exit_code; + +}; + +DaemonBase* DaemonBase::instance = nullptr; + +} /* End namespace osdev::components::daemon */ \ No newline at end of file diff --git a/include/daemonconfig.h b/include/daemonconfig.h new file mode 100644 index 0000000..4abc498 --- /dev/null +++ b/include/daemonconfig.h @@ -0,0 +1,105 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ****************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "daemonlog.h" + +// TODO: Do a better config parsing. +namespace osdev::components::daemon +{ + struct daemonconfig + { + std::map values; + + std::string get(const std::string &key) const + { + auto it = values.find(key); + if (it != values.end()) + { + return it->second; + } + return ""; + } + + static daemonconfig from_file(const std::string &filename) + { + daemonconfig cfg; + if (filename.empty()) + { + return cfg; + } + + auto split = [](const std::string &str, char delim) + { + std::vector parts; + std::stringstream oss(str); + std::string part; + while (std::getline(oss, part, delim)) + { + parts.push_back(part); + } + return parts; + }; + + auto trim = [](std::string &s) + { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch){ return !std::isspace(ch);})); + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch){return !std::isspace(ch);}).base(), s.end()); + }; + + std::ifstream ifs{filename}; + std::string line; + + while(std::getline(ifs, line)) + { + trim(line); + if (line.empty()) // skip empty lines + { + continue; + } + + if (line[0] == '#') // skip comments + { + continue; + } + + auto parts = split(line, '='); + std::string key = parts[0]; + std::string value = parts[1]; + trim(key); + trim(value); + cfg.values[key] = value; + } + ifs.close(); + return cfg; + } + }; +} \ No newline at end of file diff --git a/include/daemonlog.h b/include/daemonlog.h new file mode 100644 index 0000000..04ab170 --- /dev/null +++ b/include/daemonlog.h @@ -0,0 +1,157 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ****************************************************************************/ +#pragma once + +#include +#include +#include +#include + +namespace osdev::components::daemon { + +class daemonlog +{ +public: + /** + * Initialize the logger + * @param daemon_name + */ + static void init(const std::string &daemon_name) + { + // m_daemon_name = daemon_name; + openlog(daemon_name.c_str(), LOG_PID, LOG_DAEMON); + } + + /** + * main logger with priority LOG_X + * @param message + * @param priority + */ + static void log(const std::string &message, std::int32_t priority) + { + syslog(priority, "%s", message.c_str()); + } + + /** + * debug-level messages + * @param message + */ + static void debug(const std::string &message) + { + log(message, LOG_DEBUG); + } + + /** + * informational + * @param message + */ + static void info(const std::string &message) + { + log(message, LOG_INFO); + } + + /** + * normal but significant condition + * @param message + */ + static void notice(const std::string &message) + { + log(message, LOG_NOTICE); + } + + /** + * Warning conditions + * @param message + */ + static void warning(const std::string &message) + { + log(message, LOG_WARNING); + } + + /** + * error conditions + * @param message + */ + static void error(const std::string &message) + { + log(message, LOG_ERR); + } + + /** + * critical conditions + * @param message + */ + static void critical(const std::string &message) + { + log(message, LOG_CRIT); + } + + /** + * action must be taken immediately + * @param message + */ + static void alert(const std::string &message) + { + log(message, LOG_ALERT); + } + + /** + * system is unusable + * @param message + */ + static void emergency(const std::string &message) + { + log(message, LOG_EMERG); + } + + /** + * shutdown the logger + */ + static void shutdown() + { + closelog(); + } + +private: + static std::string priority_str(std::int32_t priority) + { + switch(priority) + { + case LOG_EMERG: return "emergency"; + case LOG_ALERT: return "alert"; + case LOG_CRIT: return "critical"; + case LOG_ERR: return "error"; + case LOG_WARNING: return "warning"; + case LOG_NOTICE: return "notice"; + case LOG_INFO: return "info"; + case LOG_DEBUG: return "debug"; + default: return "unknown_priority"; + } + } + + std::string m_daemon_name; + +}; + +// std::string osdev::components::daemon::m_daemon_name{}; + +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..93002ba --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Don't call this file directly from cmake. +# TRhis file is included from the upper directory. +# +# Build rules for the daemon library + +add_executable(MyExampleDaemon + MyDaemonExample.cpp +) + +target_include_directories(MyExampleDaemon PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ../include +) + +target_link_libraries(MyExampleDaemon PRIVATE + daemonbase +) \ No newline at end of file diff --git a/test/MyDaemonExample.cpp b/test/MyDaemonExample.cpp new file mode 100644 index 0000000..93228ef --- /dev/null +++ b/test/MyDaemonExample.cpp @@ -0,0 +1,80 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ****************************************************************************/ +#include "daemonbase.h" +#include + +using namespace osdev::components::daemon; +using namespace std::chrono_literals; + +class MyExampleDaemon : public DaemonBase +{ +public: + void on_start(const daemonconfig &config) override + { + /// Called once after daemon starts automatically with system startup + /// or when you manually call `$ system,ctl start MyExampleDaemon` + + /// Initialize your code here... + + + + daemonlog::info("MyExampleDaemon::on_start(): MyExampleDaemon version: " + config.get("version") + " started successfully!"); + } + + void on_update() override + { + /// Called every DURATION set in set_update_duration()... + + /// Update your code here + + daemonlog::info("MyExampleDaemon::on_update()"); + } + + void on_stop() override + { + /// Called once before daemon is about to exit with system shutdown or when you manually call `$ systemctl stop MyExampleDaemon` + /// Cleanup your code here... + + daemonlog::info("MyExampleDaemon::on_stop()"); + } + + void on_reload(const daemonconfig &cfg) override + { + /// Called once after your daemon's config fil is updated then reloaded with `$ systemctl reload MyExampleDaemon` + /// Handle your config updates here... + + daemonlog::info("MyExampleDaemon::on_reload(): new daemon version from updated config: " + cfg.get("version")); + } +}; + + +int main(int argc, char *argv[]) +{ + MyExampleDaemon oDaemon; // Create the daemon instance. + + oDaemon.set_name("MyAweSomeExampleDaemon"); // Set daemon name to identify logs in syslog + oDaemon.set_update_duration(3s); // Set duration to sleep before triggering the on_update callback 3 seconds. + oDaemon.set_cwd("/root"); // set daemon's current working directory to roots home-folder + oDaemon.run(argc, argv); // run the daemon + + return(EXIT_SUCCESS); // Close the main process now the child process is running. +} \ No newline at end of file -- libgit2 0.21.4