diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..38bb9ef --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +* +!src +!interfaces +!src +!tests +!example +!cmake +!CMakeLists.txt \ No newline at end of file diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 0000000..82bf9d6 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,83 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} + + - name: Upload results to path + uses: actions/upload-artifact@v4 + if: failure() + with: + name: error-log + path: ${{ steps.strings.outputs.build-output-dir }}/Testing/Temporary/LastTest.log + if-no-files-found: ignore diff --git a/CMakeLists.txt b/CMakeLists.txt index 46dd6ef..26ac1a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ -cmake_minimum_required(VERSION 3.3.2) +cmake_minimum_required(VERSION 3.5) +cmake_policy(SET CMP0077 NEW) project(skeleton VERSION 0.1 @@ -6,29 +7,19 @@ project(skeleton LANGUAGES CXX ) -include(CTest) +option(SKELETON_BUILD_TESTS "Build test program" ON) +option(SKELETON_BUILD_EXAMPLE "Build example" ON) -set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") +# Build without tchar = wchar_t +set(UNICODE OFF CACHE INTERNAL "" FORCE) -include(FetchContent) -set(FETCHCONTENT_QUIET FALSE) -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip -) - -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +add_subdirectory(src) -FetchContent_MakeAvailable(googletest) +if (SKELETON_BUILD_EXAMPLE) + add_subdirectory(example) +endif (SKELETON_BUILD_EXAMPLE) -#include(DownloadProject.cmake) -#download_project(PROJ googletest -# GIT_TAG release-1.10.0 -# GIT_REPOSITORY https://github.com/google/googletest -# PREFIX .cmakeDownload -# BINARY_DIR ${CMAKE_BINARY_DIR}/googletest-build -# ${UPDATE_DISCONNECTED_IF_AVAILABLE} -#) -add_subdirectory(log4cplus) -add_subdirectory(src) +if (SKELETON_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif (SKELETON_BUILD_TESTS) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..22b8c99 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM ubuntu:22.04 + +RUN apt-get update +RUN apt-get install build-essential -y +RUN apt-get install cmake -y +RUN apt-get install git -y + +LABEL org.opencontainers.image.title="Ubuntu cmake build" \ + org.opencontainers.image.description="Build cmake based application" \ + org.opencontainers.image.authors="@justtuomas" + +# Create directory in container image for app code +RUN mkdir -p /usr/src/app/build + +# Copy app code (..) to /usr/src/app in container image +COPY . /usr/src/app + +# Set working directory context +WORKDIR /usr/src/app/build + +RUN cmake .. + +RUN cmake --build . diff --git a/README.md b/README.md index 089634a..22d3bad 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,62 @@ -# Preparations -This guideline was written on Ubuntu 20.04 TLS. -The example uses ~/build as the base path. +# cpp-skeleton +The initial purpose of this project was to produce working example of building a cpp application with logging (l0g4cplus). -## Required build tools -The cpp-skeleton application software requires cmake v3.3.2 to build. -During initial testing g++-10 compiler was used but the ubuntu default g++-9 should work just fine. +The future vision is to serve as a skeleton for cpp application with configurable modules. -Following is the list of tool identified reconstruct the build setup on stock ubuntu. +The modules could be something like: +
+SKELETON_USE_LOG4CPLUS +
+SKELETON_USE_SOMEMODULE -```bash -sudo apt-get update -sudo apt-get install autoconf autogen automake cmake curl git libtool g++ make unzip -``` -### About Used DownloadProject -The https://github.com/Crascit/DownloadProject cmake files are used to download googletest at CMake's configure step. +## Build instructions +You can build any tool supporting digesting the `CMakeLists.txt` including vscode or vstudio but the workflow and configuration is tool specific. -And I quote from the repository README.md: -> The primary advantage of this is that the project's source code can then be included directly in the main CMake build using the add_subdirectory() command, making all of the external project's targets, etc. available without any further effort. +The following is the instructions on how to build using docker and ubuntu. +The ubuntu example is based on the docker build and is basically just overview of it. -## Build log4cplus (REL_2_1_0) -Opting to use version tagged `REL_2_1_0` as the master head version (3.0.0) requires a compiler with c++20 features and is a bit picky while at it. -For example the g++-10 (10.3) compiler accepts the flag -std=c++20 and __cplusplus evaluates to 201707L. -The log4cplus compiler test still fails while processing following version checking line. -``` -#https://github.com/log4cplus/log4cplus/blob/d9521ad97ba781b8b97f5aa29b0f4476074db866/m4/ax_cxx_compile_stdcxx.m4#L990 -#elif __cplusplus < 202002L && !defined _MSVER -``` -#### Run the following commands: -```bash -mkdir -p ~/build/3rdparty && cd ~/build/3rdparty -git clone --branch REL_2_1_0 https://github.com/log4cplus/log4cplus.git -cd log4cplus -git submodule update --init --recursive -./configure -# running the make twice due to error on the first run -# configure.ac:453: error: possibly undefined macro: AC_CHECK_INCLUDES_DEFAULT -make -j4 || make -j4 -sudo make install -sudo ldconfig -# verify installed -pkg-config --debug log4cplus -``` -# Skeleton application and tests -### Downloading the source -```bash -mkdir -p ~/build/application-software && cd ~/build/application-software +## Preparations +This guideline was written on Windows 10. + +### Required build tools +Docker installed. +[docker-windows](https://docs.docker.com/desktop/setup/install/windows-install/) +Git installed +[git-windows](https://git-scm.com/downloads/win) +#### Docker build and run example: +```shell git clone https://github.com/head5man/cpp-skeleton.git cd cpp-skeleton +# Build the image defined in ./Dockerfile +docker build -t build_image_name . +# Run container with interactive terminal +docker run -it --name=build_container_name build_image_name /bin/bash +# To run test executable +build_container_name:/usr/src/app/build# ./tests/skeleton-test +# To run application example +build_container_name:/usr/src/app/build# cd example && ./skeleton-example +# Exit and delete container and image +build_container_name:/usr/src/app/build# exit +docker rm build_container_name +docker rmi build_image_name ``` -### Running the build: -```bash -mkdir -p build && cd build -cmake .. -make -``` -### Running the application -```bash -#Run the skeleton executable -src/skeleton -``` -### Running the tests + +#### Ubuntu 22.04 build and run example ```bash -#Run the skeleton-test executable -src/skeleton-test -#Run CTest alternative reporting only module level results -make test -``` +apt-get update +apt-get install build-essential -y +apt-get install cmake -y +apt-get install git -y +# Clone repository to home and build +cd ~ +git clone https://github.com/head5man/cpp-skeleton.git +cmake -S cpp-skeleton -B cpp-skelton/build +cmake --build cpp-skeleton/build + +# To run test executable +cpp-skeleton/build/tests/skeleton-test + +# To run application example +cd cpp-skeleton/build/example && ./skeleton-example +``` diff --git a/cmake/common.cmake b/cmake/common.cmake new file mode 100644 index 0000000..9b10631 --- /dev/null +++ b/cmake/common.cmake @@ -0,0 +1,19 @@ +include(CMakePrintHelpers) + +macro (print_target_properties NAME) + cmake_print_properties( + TARGETS ${NAME} + PROPERTIES ${ARGN} + ) +endmacro() + +macro(copy_target_runtime_dlls NAME) + add_custom_command(TARGET ${NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMAND_EXPAND_LISTS + ) +endmacro() + +macro(silence_msvc_warnings NAME) + target_compile_definitions(${NAME} PRIVATE "" _CRT_SECURE_NO_WARNINGS) +endmacro() \ No newline at end of file diff --git a/cmake/googletest.cmake b/cmake/googletest.cmake new file mode 100644 index 0000000..c6f80bb --- /dev/null +++ b/cmake/googletest.cmake @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.5) + +# A separate subdirectory for building Google Test so that C++ is not +# globally enabled + +enable_language(CXX) + +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.16.0 + # URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) + +set (BUILD_SHARED_LIBS FALSE) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS OFF) + +FetchContent_MakeAvailable(googletest) diff --git a/cmake/log4cplus.cmake b/cmake/log4cplus.cmake new file mode 100644 index 0000000..849753f --- /dev/null +++ b/cmake/log4cplus.cmake @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.5) + +set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") +set(LOG4CPLUS_BUILD_TESTING OFF CACHE INTERNAL "" FORCE) + +include(FetchContent) + +FetchContent_Declare(log4cplus + GIT_REPOSITORY https://github.com/log4cplus/log4cplus.git + GIT_TAG 2.1.x + GIT_PROGRESS TRUE +) + +FetchContent_MakeAvailable(log4cplus) + +print_target_properties(log4cplus::log4cplus + LINK_LIBRARIES + COMPILE_DEFINITIONS + ) \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..026aab9 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,41 @@ +# cmake list called from root/CMakeLists.txt +# requires: skeleton-lib to be available +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/common.cmake) + +# TODO: relocate includes to appropriate folders +include_directories(${CMAKE_CURRENT_LIST_DIR}/.. ${CMAKE_CURRENT_LIST_DIR}/../src) + +# create custom target copying the logger configuration +# to the binary folder +set (LOG_CONF "log.conf") +if (MSVC) + set (LOG_PFX "$/") +endif() + +set (TARGET_LOG_CONF "${LOG_PFX}${LOG_CONF}") + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_LOG_CONF}" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/${LOG_CONF}" "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_LOG_CONF}" + DEPENDS "${CMAKE_CURRENT_LIST_DIR}/${LOG_CONF}" +) + +add_custom_target(configuration ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_LOG_CONF}") + +# skeleton example executable +add_executable(${PROJECT_NAME}-example ${CMAKE_CURRENT_LIST_DIR}/main.cpp) +silence_msvc_warnings(${PROJECT_NAME}-example) +target_include_directories(${PROJECT_NAME}-example PUBLIC ${log4cplus_SOURCE_DIR}/include) +target_link_libraries(${PROJECT_NAME}-example ${log4cplus} ${PROJECT_NAME}-lib) +if (MSVC) + copy_target_runtime_dlls(${PROJECT_NAME}-example) +endif() +print_target_properties(${PROJECT_NAME}-example COMPILE_DEFINITIONS) + +cmake_print_variables(CMAKE_CURRENT_BINARY_DIR LOG_CONF TARGET_LOG_CONF) + diff --git a/src/log.conf b/example/log.conf similarity index 77% rename from src/log.conf rename to example/log.conf index daa1bd1..914eab3 100644 --- a/src/log.conf +++ b/example/log.conf @@ -1,7 +1,7 @@ log4cplus.rootLogger=INFO, Main, Log4jUdp log4cplus.appender.Main=log4cplus::ConsoleAppender log4cplus.appender.Main.layout=log4cplus::PatternLayout -log4cplus.appender.Main.layout.ConversionPattern=%20.20F(%3L): %-5p %c %x - %m%n +log4cplus.appender.Main.layout.ConversionPattern=[%.15F:%L] %.5p [%.-5c] - %m%n log4cplus.appender.Log4jUdp=log4cplus::Log4jUdpAppender log4cplus.appender.Log4jUdp.host=127.0.0.1 diff --git a/example/main.cpp b/example/main.cpp new file mode 100644 index 0000000..b83ac50 --- /dev/null +++ b/example/main.cpp @@ -0,0 +1,40 @@ +/** + * @file main.cpp + * @author Tuomas Lahtinen (tuomas123lahtinen@gmail.com) + * @brief + * @version 0.1 + * @date 2023-04-03 + * + * @copyright Copyright (c) 2023 + * + */ + +#include "Factory.h" +#include "Logger.h" + +#include +#include +#include +#include + +#include + +int main(int argc, char **argv) +{ + #ifdef UNICODE + std::cout << __builtin_FILE() << " - UNICODE" << std::endl; + #else + std::cout << __builtin_FILE() << " - NOT UNICODE" << std::endl; + #endif + + auto logger = skeleton::factory::getLogger("main"); + std::cout << "logger instance @" << logger.get() << std::endl; + auto same = skeleton::factory::getLogger("main"); + std::cout << "logger instance @" << logger.get() << "==" << same.get() << std::endl; + logger->configure("./log.conf"); + logger->debug("debug message"); + logger->warn("warning message"); + logger->info("info message"); + + return 0; +} diff --git a/googletest/CMakeLists.txt b/googletest/CMakeLists.txt deleted file mode 100644 index f3e8ec3..0000000 --- a/googletest/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 3.3.2) - -# A separate subdirectory for building Google Test so that C++ is not -# globally enabled - -enable_language(CXX) -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED YES) -set(CMAKE_CXX_EXTENSIONS OFF) - -include(FetchContent) -set(FETCHCONTENT_QUIET FALSE) -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip -) - -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - -FetchContent_MakeAvailable(googletest) \ No newline at end of file diff --git a/interfaces/ILogger.h b/interfaces/ILogger.h index e9739ad..2a0898c 100644 --- a/interfaces/ILogger.h +++ b/interfaces/ILogger.h @@ -11,20 +11,33 @@ #pragma once #include +#ifdef UNICODE +# define TCHAR wchar_t +# define TOLOGSTR(a) (const TCHAR*)L""##a +#else // (UNICODE) +# define TCHAR char +# define TOLOGSTR(a) a +#endif // (UNICODE) + +#define TSTR std::basic_string, std::allocator> +#define TOLOGTSTR(a) TSTR(TOLOGSTR(a)) + namespace skeleton::interfaces { +enum class LogLevels +{ + TRACE=0, + DEBUG, + INFO, + WARN, + ERROR, + COUNT +}; + +template struct ILogger { - enum class LogLevels - { - TRACE=0, - DEBUG, - INFO, - WARN, - ERROR, - COUNT - }; virtual ~ILogger() = default; /// @brief log function prototype /// @param logger logger name (facility) @@ -33,12 +46,12 @@ struct ILogger /// @param file grabbed log location details /// @param line grabbed log location details /// @param function grabbed log location details - virtual void log(const char* logger, LogLevels level, const std::string &message, const char* file, int line, const char* function) = 0; + virtual void log(const TChar* logger, LogLevels level, const std::basic_string, std::allocator> &message, const char* file, int line, const char* function) = 0; /// @brief configure functionality is implementation dependent /// e.g. log4cplus it is required that it is called at least once /// @param filename - virtual void configure(const std::string &filename) = 0; + virtual void configure(const TChar* const filename) = 0; }; } \ No newline at end of file diff --git a/interfaces/LoggerLog4Cplus.h b/interfaces/LoggerLog4Cplus.h index e59af73..1e5756e 100644 --- a/interfaces/LoggerLog4Cplus.h +++ b/interfaces/LoggerLog4Cplus.h @@ -19,33 +19,42 @@ namespace skeleton::implementations { - -class Log4CplusLogger: public interfaces::ILogger +template +class Log4CplusLogger: public interfaces::ILogger { + log4cplus::Initializer initializer; + using LogLevels = interfaces::LogLevels; + using _Mystr = std::basic_string, std::allocator>; + using _Myss = std::basic_stringstream, std::allocator>; public: - Log4CplusLogger() {} + Log4CplusLogger() { } - ~Log4CplusLogger() {std::cout << "destroyed" << std::endl;} + ~Log4CplusLogger() + { + std::cout << "destroyed" << std::endl; + } - void log(const char* logger, interfaces::ILogger::LogLevels level, const std::string &message, const char* file, int line, const char* function) override + void log(const TChar* logger, LogLevels level, const _Mystr &message, const char* file, int line, const char* function) override { - log4cplus::Logger instance(log4cplus::Logger::getInstance(LOG4CPLUS_TEXT(logger))); + log4cplus::Logger instance(log4cplus::Logger::getInstance(_Mystr(logger))); instance.log(m_logLevels.at(level), message, file, line, function); } - void configure(const std::string &file) + void configure(const TChar* const file) { - using namespace log4cplus; + log4cplus::Logger instance(log4cplus::Logger::getInstance(TOLOGTSTR("init"))); std::ifstream ifs(file); if (ifs.good()) { - PropertyConfigurator config(file); + log4cplus::PropertyConfigurator config(file); config.configure(); + instance.log(log4cplus::INFO_LOG_LEVEL, TOLOGSTR("configure() - file OK")); } else { - BasicConfigurator config; + log4cplus::BasicConfigurator config; config.configure(); + instance.log(log4cplus::INFO_LOG_LEVEL, TOLOGSTR("Configure() - no file - BasicConfigurator")); } } diff --git a/log4cplus/CMakeLists.txt b/log4cplus/CMakeLists.txt deleted file mode 100644 index e28f098..0000000 --- a/log4cplus/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.3.2) - -set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1") - -include(FetchContent) -set(FETCHCONTENT_QUIET FALSE) - -FetchContent_Declare(log4cplus - GIT_REPOSITORY https://github.com/log4cplus/log4cplus.git - GIT_TAG REL_2_0_6 - GIT_PROGRESS TRUE -) - -macro(FetchContent_MakeAvailableWithConfigure NAME) - FetchContent_GetProperties(${NAME}) - if(NOT ${NAME}_POPULATED) - FetchContent_Populate(${NAME}) - add_custom_command ( OUTPUT ${${NAME}_SOURCE_DIR}/include/log4cplus/config/defines.hxx - COMMAND ${${NAME}_SOURCE_DIR}/configure - WORKING_DIRECTORY ${${NAME}_SOURCE_DIR}/) - add_custom_target(${NAME}_configure ALL DEPENDS ${${NAME}_SOURCE_DIR}/include/log4cplus/config/defines.hxx -) - add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR}) - endif() -endmacro() -FetchContent_MakeAvailableWithConfigure(log4cplus) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 367d2c2..a0a2e2a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,45 +1,22 @@ -cmake_minimum_required(VERSION 3.3.2) +cmake_minimum_required(VERSION 3.5) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED YES) -# Main source directory -include_directories(${CMAKE_CURRENT_LIST_DIR}/..) - -# create custom target copying the logger configuration -# to the binary folder -set (conf_FILE log.conf) -add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${conf_FILE}" - COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_LIST_DIR}/${conf_FILE}" "${CMAKE_CURRENT_BINARY_DIR}/${conf_FILE}" - DEPENDS "${CMAKE_CURRENT_LIST_DIR}/${conf_FILE}" -) -add_custom_target(configuration ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/log.conf") +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/common.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/log4cplus.cmake) list(APPEND SourceFiles ${CMAKE_CURRENT_LIST_DIR}/Factory.cpp ) -list(APPEND TestFiles - ${CMAKE_CURRENT_LIST_DIR}/../test/TestLogging.cpp -) - -# skeleton main executable -add_executable(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/main.cpp ${SourceFiles}) -message(${log4cplus_SOURCE_DIR}/include) -target_include_directories(${PROJECT_NAME} PUBLIC ${log4cplus_SOURCE_DIR}/include) -target_link_libraries(${PROJECT_NAME} PRIVATE log4cplus) - # skeleton sources as library -add_library(${PROJECT_NAME}-lib ${SourceFiles}) -target_include_directories(${PROJECT_NAME}-lib PUBLIC ${CMAKE_CURRENT_LIST_DIR}/.. ${log4cplus_SOURCE_DIR}/include) -target_link_libraries(${PROJECT_NAME}-lib PRIVATE log4cplus) +add_library(${PROJECT_NAME}-lib STATIC ${SourceFiles}) -# skeleton test executable -add_executable(${PROJECT_NAME}-test ${CMAKE_CURRENT_LIST_DIR}/../test/TestMain.cpp) -target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}-lib gtest gmock_main) -target_include_directories(${PROJECT_NAME}-test PUBLIC ${log4cplus_SOURCE_DIR}/include) +if (MSVC) + silence_msvc_warnings(${PROJECT_NAME}-lib) +endif() -add_test(NAME ${PROJECT_NAME}-test COMMAND ${PROJECT_NAME}-test) - -install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +target_include_directories(${PROJECT_NAME}-lib PUBLIC ${CMAKE_CURRENT_LIST_DIR}/.. ${log4cplus_SOURCE_DIR}/include) +target_link_libraries(${PROJECT_NAME}-lib log4cplus::log4cplus) +print_target_properties(${PROJECT_NAME}-lib COMPILE_DEFINITIONS) diff --git a/src/Factory.cpp b/src/Factory.cpp index cc23645..7309f9b 100644 --- a/src/Factory.cpp +++ b/src/Factory.cpp @@ -15,9 +15,28 @@ namespace skeleton::factory { -template <> std::unique_ptr getDefault(const char* str) +template <> std::shared_ptr getLogger(const char* name) { - return std::unique_ptr(new logging::Logger(str, std::make_unique())); + static std::map> loggers; + std::shared_ptr p = nullptr; + + for (auto [key, value] : loggers) + { + if (strcmp(name, key) == 0) + { + std::cout << "use found logger" << std::endl; + p = value.lock(); + } + } + + if (p == nullptr) + { + std::cout << "create logger" << std::endl; + p = std::make_shared(name, std::make_unique>()); + loggers.insert_or_assign(name, std::weak_ptr(p)); + } + + return p; } } // skeleton::factory diff --git a/src/Factory.h b/src/Factory.h index e95e743..8912c16 100644 --- a/src/Factory.h +++ b/src/Factory.h @@ -14,5 +14,5 @@ namespace skeleton::factory { // Get a default implementation of class - template std::unique_ptr getDefault(D); + template std::shared_ptr getLogger(D); } // namespace skeleton::factory diff --git a/src/Logger.h b/src/Logger.h index 27e470a..8bd6c83 100644 --- a/src/Logger.h +++ b/src/Logger.h @@ -10,32 +10,44 @@ */ #pragma once #include "interfaces/ILogger.h" -#include +#include #include +#include +#include namespace skeleton::logging { -#if USE_LOG_MACRO - // LOG macro - // declare 'const char* logger = "somemodule"' in the user file - #define LOG(level, logEvent) \ - do { std::stringstream _s; _s << logEvent; log4cplus::Logger::getInstance(LOG4CPLUS_TEXT(logger)).log(level, _s.str()); } while(false) -#endif - /// @brief Logger class provides functions for application logging class Logger { #define logwrapper_ARGS_CAPTURE const char *file = __builtin_FILE(), int line = __builtin_LINE(), const char *function = __builtin_FUNCTION() #define logwrapper_CAPTURED_ARGS file, line, function - using Levels = interfaces::ILogger::LogLevels; + using Levels = interfaces::LogLevels; + using _Mystr = TSTR; + using _Myss = std::basic_stringstream, std::allocator>; public: - Logger(const char *name, std::unique_ptr logger): - m_facility(name), - m_logimpl(std::move(logger)) - {} + Logger(const char *name, std::shared_ptr> logger): + m_logimpl(logger) + { + size_t newsize = strlen(name) + 1; + #ifdef UNICODE + m_facility = std::make_unique(newsize); + // Convert char* string to a wchar_t* string. + size_t convertedChars = 0; + mbstowcs_s(&convertedChars, m_facility.get(), newsize, name, _TRUNCATE); + #else + m_facility = std::make_unique(newsize); + strcpy(m_facility.get(), name); + #endif + } + + ~Logger() + { + m_logimpl.reset(); + } Logger() = delete; Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; @@ -82,20 +94,30 @@ class Logger template void log(Levels level, const T &output, logwrapper_ARGS_CAPTURE) { - std::stringstream ss(output); - m_logimpl->log(m_facility, level, ss.str().c_str(), file, line, function); + _Myss ss; + ss << output; + m_logimpl->log(m_facility.get(), level, ss.str(), file, line, function); } /// @brief implementation dependent configure /// @param configfile - void configure(const std::string &configfile = "") + void configure(const char* const configFile = "") { - m_logimpl->configure(configfile); + #ifdef UNICODE + size_t newsize = strlen(configFile) + 1; + std::unique_ptr converted = std::make_unique(newsize); + // Convert char* string to a wchar_t* string. + size_t convertedChars = 0; + mbstowcs_s(&convertedChars, converted.get(), newsize, configFile, _TRUNCATE); + m_logimpl->configure(converted.get()); + #else + m_logimpl->configure(configFile); + #endif } private: - const char *m_facility{nullptr}; - std::unique_ptr m_logimpl{nullptr}; + std::shared_ptr m_facility{nullptr}; + std::shared_ptr> m_logimpl{nullptr}; }; } // namespace skeleton::logging \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 8b61bbc..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @file main.cpp - * @author Tuomas Lahtinen (tuomas123lahtinen@gmail.com) - * @brief - * @version 0.1 - * @date 2023-04-03 - * - * @copyright Copyright (c) 2023 - * - */ - - -#include "Factory.h" -#include "Logger.h" - -int main(int argc, char **argv) -{ - auto logger = skeleton::factory::getDefault("main"); - logger->configure("log.conf"); - logger->debug("debug message"); - logger->warn("warning message"); - logger->info("info message"); - return 0; -} diff --git a/test/LoggerTest.cpp b/test/LoggerTest.cpp deleted file mode 100644 index ab769a3..0000000 --- a/test/LoggerTest.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @file LoggerTest.cpp - * @author Tuomas Lahtinen (tuomas123lahtinen@gmail.com) - * @brief - * @version 0.1 - * @date 2023-04-03 - * - * @copyright Copyright (c) 2023 - * - */ - -#include "../interfaces/ILogger.h" -#include "../src/Logger.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include -#include - -namespace skeleton::testing -{ -class MockLogger : public interfaces::ILogger -{ -public: - MOCK_METHOD(void, configure, (const std::string& filename), (override)); - MOCK_METHOD(void, log, ( - const char* logger, - interfaces::ILogger::LogLevels level, - const std::string &message, - const char* file, - int line, - const char* function - ), (override)); -}; - -class LoggerTest: public ::testing::Test -{ - protected: - void SetUp() override - { - auto mock = std::unique_ptr(new MockLogger()); - p_mock = static_cast(mock.get()); - m_logger = std::unique_ptr(new logging::Logger("mocked", std::move(mock))); - } - - void TearDown() override - { - m_logger.reset(); - } - - std::unique_ptr m_logger; - MockLogger* p_mock{nullptr}; -}; - -TEST_F(LoggerTest, LogAllLevels_ExpectLocationAndMessage) -{ - using ::testing::_; - using ::testing::InSequence; - using levels = skeleton::interfaces::ILogger::LogLevels; - InSequence seq; - std::stringstream ss(); - auto file = __builtin_FILE(); - auto function = __builtin_FUNCTION(); - auto line = __builtin_LINE(); - EXPECT_CALL(*p_mock, log("mocked", levels::TRACE, "trace", file, ++line + 5, function)).Times(1); - EXPECT_CALL(*p_mock, log("mocked", levels::DEBUG, "debug", file, ++line + 5, function)).Times(1); - EXPECT_CALL(*p_mock, log("mocked", levels::INFO, "info", file, ++line + 5, function)).Times(1); - EXPECT_CALL(*p_mock, log("mocked", levels::WARN, "warn", file, ++line + 5, function)).Times(1); - EXPECT_CALL(*p_mock, log("mocked", levels::ERROR, "error", file, ++line + 5, function)).Times(1); - m_logger->trace("trace"); - m_logger->debug("debug"); - m_logger->info("info"); - m_logger->warn("warn"); - m_logger->error("error"); -} - -TEST_F(LoggerTest, LogAllLevels_ExpectEachLevelCalledOnce) -{ - using ::testing::_; - using ::testing::InSequence; - using levels = skeleton::interfaces::ILogger::LogLevels; - InSequence seq; - EXPECT_CALL(*p_mock, log("mocked", levels::TRACE, _, _, _, _)).Times(1); - EXPECT_CALL(*p_mock, log("mocked", levels::DEBUG, _, _, _, _)).Times(1); - EXPECT_CALL(*p_mock, log("mocked", levels::INFO, _, _, _, _)).Times(1); - EXPECT_CALL(*p_mock, log("mocked", levels::WARN, _, _, _, _)).Times(1); - EXPECT_CALL(*p_mock, log("mocked", levels::ERROR, _, _, _, _)).Times(1); - for (int i = 0; i < static_cast(levels::COUNT); i++) - { - auto level = static_cast(i); - m_logger->log(level, "log"); - } -} - -TEST_F(LoggerTest, ConfigureWithFilename_ExpectConfigureCallWithFilename) -{ - const char* filename = "filename"; - EXPECT_CALL(*p_mock, configure(filename)).Times(1); - m_logger->configure(filename); -} - -TEST_F(LoggerTest, Configure_ExpectConfigureCall) -{ - using ::testing::_; - EXPECT_CALL(*p_mock, configure("")).Times(1); - m_logger->configure(); -} - -} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..b4db068 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,30 @@ +include(CTest) + +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/googletest.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/common.cmake) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) + +list(APPEND TestFiles + ${CMAKE_CURRENT_LIST_DIR}/TestMain.cpp + ${CMAKE_CURRENT_LIST_DIR}/LoggerTest.cpp +) + +add_executable(${PROJECT_NAME}-test ${TestFiles}) + +if (MSVC) + silence_msvc_warnings(${PROJECT_NAME}-test) +endif() + +target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}-lib gtest gmock gmock_main) + +target_include_directories(${PROJECT_NAME}-test PUBLIC ${log4cplus_SOURCE_DIR}/include) + +print_target_properties(${PROJECT_NAME}-test LINK_LIBRARIES) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}-test) \ No newline at end of file diff --git a/tests/LoggerTest.cpp b/tests/LoggerTest.cpp new file mode 100644 index 0000000..c44f6bd --- /dev/null +++ b/tests/LoggerTest.cpp @@ -0,0 +1,108 @@ +/** + * @file LoggerTest.cpp + * @author Tuomas Lahtinen (tuomas123lahtinen@gmail.com) + * @brief + * @version 0.1 + * @date 2023-04-03 + * + * @copyright Copyright (c) 2023 + * + */ + +#include "../interfaces/ILogger.h" +#include "../src/Logger.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include + +namespace skeleton::testing +{ + +class MockLogger : public interfaces::ILogger +{ +public: + MOCK_METHOD(void, configure, (const TCHAR* const filename), (override)); + MOCK_METHOD(void, log, ( + const TCHAR* logger, + interfaces::LogLevels level, + (const TSTR &message), + const char* file, + int line, + const char* function + ), (override)); +}; + +class LoggerTest: public ::testing::Test +{ + protected: + void SetUp() override + { + } + + void TearDown() override + { + } +}; + +TEST_F(LoggerTest, LogAllLevels_ExpectLocationAndMessage) +{ + auto m_mock = std::make_shared(); + auto m_logger = std::unique_ptr(new logging::Logger("mocked", m_mock)); + using ::testing::_; + using levels = skeleton::interfaces::LogLevels; + + auto line = __builtin_LINE() + 6; + ::testing::InSequence seq; + EXPECT_CALL(*m_mock, log(_, levels::TRACE, TOLOGTSTR("trace"), ::testing::StrEq(__builtin_FILE()), ++line, ::testing::StrEq(__builtin_FUNCTION()))).Times(1); + EXPECT_CALL(*m_mock, log(_, levels::DEBUG, TOLOGTSTR("debug"), ::testing::StrEq(__builtin_FILE()), ++line, ::testing::StrEq(__builtin_FUNCTION()))).Times(1); + EXPECT_CALL(*m_mock, log(_, levels::INFO, TOLOGTSTR("info"), ::testing::StrEq(__builtin_FILE()), ++line, ::testing::StrEq(__builtin_FUNCTION()))).Times(1); + EXPECT_CALL(*m_mock, log(_, levels::WARN, TOLOGTSTR("warn"), ::testing::StrEq(__builtin_FILE()), ++line, ::testing::StrEq(__builtin_FUNCTION()))).Times(1); + EXPECT_CALL(*m_mock, log(_, levels::ERROR, TOLOGTSTR("error"), ::testing::StrEq(__builtin_FILE()), ++line, ::testing::StrEq(__builtin_FUNCTION()))).Times(1); + m_logger->trace("trace"); + m_logger->debug("debug"); + m_logger->info("info"); + m_logger->warn("warn"); + m_logger->error("error"); +} + +TEST_F(LoggerTest, LogAllLevels_ExpectEachLevelCalledOnce) +{ + auto m_mock = std::make_shared(); + auto m_logger = std::unique_ptr(new logging::Logger("mocked", m_mock)); + using ::testing::_; + using ::testing::InSequence; + using levels = skeleton::interfaces::LogLevels; + InSequence seq; + + EXPECT_CALL(*m_mock, log(_, levels::TRACE, _, _, _, _)).Times(1); + EXPECT_CALL(*m_mock, log(_, levels::DEBUG, _, _, _, _)).Times(1); + EXPECT_CALL(*m_mock, log(_, levels::INFO, _, _, _, _)).Times(1); + EXPECT_CALL(*m_mock, log(_, levels::WARN, _, _, _, _)).Times(1); + EXPECT_CALL(*m_mock, log(_, levels::ERROR, _, _, _, _)).Times(1); + for (int i = 0; i < static_cast(levels::COUNT); i++) + { + auto level = static_cast(i); + m_logger->log(level, "log"); + } +} + +TEST_F(LoggerTest, ConfigureWithFilename_ExpectConfigureCallWithFilename) +{ + auto m_mock = std::make_shared(); + auto m_logger = std::unique_ptr(new logging::Logger("mocked", m_mock)); + EXPECT_CALL(*m_mock, configure(::testing::StrEq(TOLOGTSTR("filename")))).Times(1); + m_logger->configure("filename"); +} + +TEST_F(LoggerTest, Configure_ExpectConfigureCall) +{ + auto m_mock = std::make_shared(); + auto m_logger = std::unique_ptr(new logging::Logger("mocked", m_mock)); + using ::testing::_; + EXPECT_CALL(*m_mock, configure(_)).Times(1); + m_logger->configure(); +} + +} \ No newline at end of file diff --git a/test/TestMain.cpp b/tests/TestMain.cpp similarity index 88% rename from test/TestMain.cpp rename to tests/TestMain.cpp index 769774a..cfab4bb 100644 --- a/test/TestMain.cpp +++ b/tests/TestMain.cpp @@ -8,8 +8,6 @@ * @copyright Copyright (c) 2023 * */ -// test includes -#include "LoggerTest.cpp" // googletest #include "gtest/gtest.h"