From 0a43281c4db288bcf24c5f02e904b09224b996a5 Mon Sep 17 00:00:00 2001 From: Stefano Lusardi Date: Fri, 10 Jan 2025 08:44:48 +0100 Subject: [PATCH] Project baseline. --- .clang-format | 18 ++ .clang-tidy | 8 + .gitignore | 16 ++ .pre-commit-config.yaml | 15 ++ CHANGELOG | 4 + CMakeLists.txt | 22 ++ LICENSE | 202 ++++++++++++++++++ VERSION | 1 + cmake/benchmarks.cmake | 9 + cmake/clang_format.cmake | 34 +++ cmake/clang_tidy.cmake | 36 ++++ cmake/compiler_info.cmake | 21 ++ cmake/coverage.cmake | 8 + cmake/cppcheck.cmake | 30 +++ cmake/cpplint.cmake | 24 +++ cmake/doxygen.cmake | 50 +++++ cmake/examples.cmake | 8 + cmake/options.cmake | 54 +++++ cmake/sanitizer_address.cmake | 15 ++ cmake/sanitizer_thread.cmake | 22 ++ cmake/unit_tests.cmake | 11 + cmake/warnings.cmake | 17 ++ conanfile.py | 81 +++++++ rabbitmq_client/CMakeLists.txt | 83 +++++++ rabbitmq_client/conanfile.txt | 9 + rabbitmq_client/examples/simple_client.cpp | 36 ++++ .../teiacare/rabbitmq_client/channel.hpp | 97 +++++++++ .../rabbitmq_client/rabbitmq_client.hpp | 60 ++++++ .../teiacare/rabbitmq_client/version.hpp | 37 ++++ rabbitmq_client/src/channel.cpp | 152 +++++++++++++ rabbitmq_client/src/rabbitmq_client.cpp | 74 +++++++ rabbitmq_client/src/version.cpp.in | 22 ++ rabbitmq_client/tests/main.cpp | 21 ++ 33 files changed, 1297 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 VERSION create mode 100644 cmake/benchmarks.cmake create mode 100644 cmake/clang_format.cmake create mode 100644 cmake/clang_tidy.cmake create mode 100644 cmake/compiler_info.cmake create mode 100644 cmake/coverage.cmake create mode 100644 cmake/cppcheck.cmake create mode 100644 cmake/cpplint.cmake create mode 100644 cmake/doxygen.cmake create mode 100644 cmake/examples.cmake create mode 100644 cmake/options.cmake create mode 100644 cmake/sanitizer_address.cmake create mode 100644 cmake/sanitizer_thread.cmake create mode 100644 cmake/unit_tests.cmake create mode 100644 cmake/warnings.cmake create mode 100644 conanfile.py create mode 100644 rabbitmq_client/CMakeLists.txt create mode 100644 rabbitmq_client/conanfile.txt create mode 100644 rabbitmq_client/examples/simple_client.cpp create mode 100644 rabbitmq_client/include/teiacare/rabbitmq_client/channel.hpp create mode 100644 rabbitmq_client/include/teiacare/rabbitmq_client/rabbitmq_client.hpp create mode 100644 rabbitmq_client/include/teiacare/rabbitmq_client/version.hpp create mode 100644 rabbitmq_client/src/channel.cpp create mode 100644 rabbitmq_client/src/rabbitmq_client.cpp create mode 100644 rabbitmq_client/src/version.cpp.in create mode 100644 rabbitmq_client/tests/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e043a97 --- /dev/null +++ b/.clang-format @@ -0,0 +1,18 @@ +--- +BasedOnStyle: Microsoft +AccessModifierOffset: '-4' +BreakConstructorInitializers: BeforeComma +ColumnLimit: '0' +IndentWidth: '4' +KeepEmptyLinesAtTheStartOfBlocks: 'false' +PointerAlignment: Left +UseTab: Never +FixNamespaceComments: 'false' +IncludeBlocks: 'Regroup' +IncludeCategories: + - Regex: '^(<|")teiacare/' + Priority: 1 + CaseSensitive: true + - Regex: '.*' + Priority: 2 +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..e41b135 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,8 @@ +--- +Checks: > + clang-*, + modernize-*', + +WarningsAsErrors: '*' +HeaderFilterRegex: '.*' +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c87f78d --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +**/__pycache__ +**/.vscode +**/.venv +**/build +**/install +**/results +**/memcheck +**/callgrind +**/.cache +**/.conan +**/.clangd +**/launch.dev.json +**/compile_commands.json +**/CMakeUserPresets.json +rabbitmq_client/src/version.cpp +/docs diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..d7950bb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v15.0.7 + hooks: + - id: clang-format + name: clang-format + files: \.cpp$|\.hpp$ diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..d75bcbf --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,4 @@ +# TeiaCareRabbitMQClient Changelog + +## [1.0.0] - 2025-xx-xx +### Added diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..772cd43 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.21 FATAL_ERROR) +file(STRINGS "VERSION" VERSION_STR) +project(teiacare_rabbitmq_client + VERSION ${VERSION_STR} + LANGUAGES CXX + HOMEPAGE_URL "https://github.com/TeiaCare/TeiaCareRabbitMQClient" + DESCRIPTION "TeiaCareRabbitMQClient is a cross-platform C++ client for RabbitMQ protocol (based on AMQP)" +) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/modules) + +include(GNUInstallDirs) +include(CMakePrintHelpers) + +include(compiler_info) +include(warnings) +include(options) +validate_project_options() + +enable_testing() +add_subdirectory(rabbitmq_client) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f49b4f7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 TeiaCare + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this project except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/cmake/benchmarks.cmake b/cmake/benchmarks.cmake new file mode 100644 index 0000000..5828055 --- /dev/null +++ b/cmake/benchmarks.cmake @@ -0,0 +1,9 @@ +function(setup_benchmarks TARGET_NAME) + set(TARGET_NAME_BENCHMARK ${TARGET_NAME}_benchmarks) + find_package(benchmark REQUIRED) + add_executable(${TARGET_NAME_BENCHMARK}) + target_sources(${TARGET_NAME_BENCHMARK} PRIVATE ${ARGN}) + target_compile_features(${TARGET_NAME_BENCHMARK} PUBLIC cxx_std_20) + target_link_libraries(${TARGET_NAME_BENCHMARK} PRIVATE benchmark::benchmark PRIVATE ${TARGET_NAME}) + install(TARGETS ${TARGET_NAME_BENCHMARK} DESTINATION benchmarks) +endfunction() diff --git a/cmake/clang_format.cmake b/cmake/clang_format.cmake new file mode 100644 index 0000000..1d1e9ac --- /dev/null +++ b/cmake/clang_format.cmake @@ -0,0 +1,34 @@ +set(VIRTUAL_ENV_BINARY_DIR "${CMAKE_SOURCE_DIR}/.venv/bin") +find_program(CLANG_FORMAT NAMES "clang-format" HINTS ${VIRTUAL_ENV_BINARY_DIR}) +find_package(Python3) + +if(NOT CLANG_FORMAT) + message(WARNING "clang-format not found!") +else() + message(STATUS "clang-format: " ${CLANG_FORMAT}) + execute_process(COMMAND ${CLANG_FORMAT} --version) + + if(NOT EXISTS "${CMAKE_SOURCE_DIR}/.clang-format") + message(FATAL_ERROR "'${CMAKE_SOURCE_DIR}/.clang-format' configuration file not found!") + endif() +endif() + +function(setup_target_clang_format TARGET_NAME TARGET_FOLDERS) + if(NOT CLANG_FORMAT) + message(WARNING "clang-format not found!") + return() + endif() + + if(NOT ${PYTHON_FOUND}) + message(WARNING "python3 not found!") + return() + endif() + + add_custom_target(${TARGET_NAME}_clang_format ALL + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/scripts/tools/run_clang_format.py + --executable ${CLANG_FORMAT} + ${TARGET_FOLDERS} + COMMENT "Running ${CLANG_FORMAT} on ${TARGET_NAME}" + ) +endfunction() diff --git a/cmake/clang_tidy.cmake b/cmake/clang_tidy.cmake new file mode 100644 index 0000000..c7c702e --- /dev/null +++ b/cmake/clang_tidy.cmake @@ -0,0 +1,36 @@ +set(VIRTUAL_ENV_BINARY_DIR "${CMAKE_SOURCE_DIR}/.venv/bin") +find_program(CLANG_TIDY NAMES "clang-tidy" HINTS ${VIRTUAL_ENV_BINARY_DIR}) +find_package(Python3) + +if(NOT CLANG_TIDY) + message(WARNING "clang-tidy not found!") +else() + message(STATUS "clang-tidy: " ${CLANG_TIDY}) + execute_process(COMMAND ${CLANG_TIDY} --version) + + if(NOT EXISTS "${CMAKE_SOURCE_DIR}/.clang-tidy") + message(FATAL_ERROR "'${CMAKE_SOURCE_DIR}/.clang-tidy' configuration file not found!") + endif() +endif() + +function(setup_target_clang_tidy TARGET_NAME) + if(NOT CLANG_TIDY) + message(WARNING "clang-tidy not found!") + return() + endif() + + if(NOT ${PYTHON_FOUND}) + message(WARNING "python3 not found!") + return() + endif() + + add_custom_target(${TARGET_NAME}_clang_tidy ALL + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/scripts/tools/run_clang_tidy.py + --executable ${CLANG_TIDY} + --compile_commands ${CMAKE_BINARY_DIR} + ${TARGET_FOLDERS} + DEPENDS ${TARGET_NAME} + COMMENT "Running ${CLANG_TIDY} on ${TARGET_NAME}" + ) +endfunction() diff --git a/cmake/compiler_info.cmake b/cmake/compiler_info.cmake new file mode 100644 index 0000000..a8ade83 --- /dev/null +++ b/cmake/compiler_info.cmake @@ -0,0 +1,21 @@ +# https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html +if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") + # GCC options +elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + # Clang options + add_compile_options(-fstandalone-debug) # Add support for std::string debug +elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") + # MSVC options +endif() + +function(get_compiler_versions) + string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) + set(COMPILER_VERSION_MAJOR ${CMAKE_MATCH_1} PARENT_SCOPE) + set(COMPILER_VERSION_MINOR ${CMAKE_MATCH_2} PARENT_SCOPE) + set(COMPILER_VERSION_PATCH ${CMAKE_MATCH_3} PARENT_SCOPE) +endfunction() + +cmake_print_variables(CMAKE_BUILD_TYPE) +cmake_print_variables(CMAKE_GENERATOR) +cmake_print_variables(CMAKE_CXX_COMPILER_ID) +cmake_print_variables(CMAKE_CXX_COMPILER_VERSION) diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake new file mode 100644 index 0000000..274ec39 --- /dev/null +++ b/cmake/coverage.cmake @@ -0,0 +1,8 @@ +function(add_coverage TARGET) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${TARGET_NAME} PUBLIC --coverage -fPIC -O0 -g) + target_link_options(${TARGET_NAME} PUBLIC --coverage -fPIC) + elseif(MSVC) + message(STATUS "Code coverage not supported on MSVC") + endif() +endfunction() diff --git a/cmake/cppcheck.cmake b/cmake/cppcheck.cmake new file mode 100644 index 0000000..e9bef23 --- /dev/null +++ b/cmake/cppcheck.cmake @@ -0,0 +1,30 @@ +find_program(CPPCHECK NAMES "cppcheck") +find_package(Python3) + +if(NOT CPPCHECK) + message(WARNING "cppcheck not found!") +else() + message(STATUS "cppcheck: " ${CPPCHECK}) + execute_process(COMMAND ${CPPCHECK} --version) +endif() + +function(setup_target_cppcheck TARGET) + if(NOT CPPCHECK) + message(WARNING "cppcheck not found!") + return() + endif() + + if(NOT ${PYTHON_FOUND}) + message(WARNING "python3 not found!") + return() + endif() + + add_custom_target(${TARGET_NAME}_cppcheck ALL + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/scripts/tools/run_cppcheck.py + ${CMAKE_BUILD_TYPE} + DEPENDS ${TARGET_NAME} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running ${CPPCHECK} on ${TARGET_NAME}" + ) +endfunction() diff --git a/cmake/cpplint.cmake b/cmake/cpplint.cmake new file mode 100644 index 0000000..5630803 --- /dev/null +++ b/cmake/cpplint.cmake @@ -0,0 +1,24 @@ +include(FindPythonInterp) +find_program(CPPLINT NAMES "cpplint") + +if(NOT CPPLINT) + message(WARNING "cpplint not found!") +else() + message(STATUS "cpplint: " ${CPPLINT}) + execute_process(COMMAND ${CPPLINT} --version) + message(STATUS "\n") + +endif() + +function(setup_target_cpplint TARGET) + if(NOT CPPLINT) + message(WARNING "cpplint not found!") + return() + endif() + add_custom_target(${TARGET}_cpplint ALL + COMMAND ${CPPLINT} "--extensions=cpp,hpp --root=${CMAKE_CURRENT_SOURCE_DIR}" ${ARGN} + DEPENDS ${TARGET_SRC} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Running ${CPPLINT} on ${TARGET}" + ) +endfunction() diff --git a/cmake/doxygen.cmake b/cmake/doxygen.cmake new file mode 100644 index 0000000..70714a3 --- /dev/null +++ b/cmake/doxygen.cmake @@ -0,0 +1,50 @@ +function(setup_docs TARGET_NAME TARGET_HEADERS TARGET_HEADERS_DIRECTORY DOXYFILE_IN) + find_package(Doxygen REQUIRED) + + if (NOT DOXYGEN_FOUND) + message(WARNING "Doxygen not found. Unable to generate docs") + return() + endif() + + set(DOXYGEN_INPUT_DIR + ${CMAKE_SOURCE_DIR}/sdk/examples/src + ${TARGET_HEADERS_DIRECTORY} + ${CMAKE_SOURCE_DIR}/README.md + ) + string(REPLACE ";" " " DOXYGEN_INPUT_DIR "${DOXYGEN_INPUT_DIR}") + + set(DOXYGEN_EXAMPLE_DIR ${CMAKE_SOURCE_DIR}/sdk/examples/src) + set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/docs) + set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/html/index.html) + set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + set(DOXYFILE_LOGO ${CMAKE_CURRENT_SOURCE_DIR}/docs/logo/logo-small.png) + set(DOXYFILE_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/docs/style/header.html) + set(DOXYFILE_FOOTER ${CMAKE_CURRENT_SOURCE_DIR}/docs/style/footer.html) + + set(DOXYFILE_EXTRA_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/docs/style/doxygen-awesome-darkmode-toggle.js + ${CMAKE_CURRENT_SOURCE_DIR}/docs/style/doxygen-awesome-fragment-copy-button.js + ) + string(REPLACE ";" " " DOXYFILE_EXTRA_FILES "${DOXYFILE_EXTRA_FILES}") + + set(DOXYFILE_EXTRA_STYLESHEET + ${CMAKE_CURRENT_SOURCE_DIR}/docs/style/doxygen-awesome.css + ${CMAKE_CURRENT_SOURCE_DIR}/docs/style/doxygen-awesome-sidebar-only.css + ${CMAKE_CURRENT_SOURCE_DIR}/docs/style/doxygen-awesome-sidebar-only-darkmode-toggle.css + ) + string(REPLACE ";" " " DOXYFILE_EXTRA_STYLESHEET "${DOXYFILE_EXTRA_STYLESHEET}") + + file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) + configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) + + add_custom_command( + OUTPUT ${DOXYGEN_INDEX_FILE} + DEPENDS ${TARGET_HEADERS} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} + COMMENT "Generating docs" + ) + add_custom_target(${TARGET_NAME}_docs ALL DEPENDS ${DOXYGEN_INDEX_FILE}) + install(DIRECTORY ${DOXYGEN_OUTPUT_DIR} DESTINATION .) +endfunction() diff --git a/cmake/examples.cmake b/cmake/examples.cmake new file mode 100644 index 0000000..17b6b31 --- /dev/null +++ b/cmake/examples.cmake @@ -0,0 +1,8 @@ +function(add_example LIB_TARGET EXAMPLE_TARGET) + find_package(spdlog REQUIRED) + set(TARGET_NAME ${EXAMPLE_TARGET}) + add_executable(${TARGET_NAME} examples/${EXAMPLE_TARGET}.cpp) + target_link_libraries(${TARGET_NAME} PRIVATE ${LIB_TARGET} PRIVATE spdlog::spdlog) + target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20) + install(TARGETS ${TARGET_NAME} DESTINATION examples) +endfunction() diff --git a/cmake/options.cmake b/cmake/options.cmake new file mode 100644 index 0000000..a21c40a --- /dev/null +++ b/cmake/options.cmake @@ -0,0 +1,54 @@ +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_CXX_EXTENSIONS True) +set(CMAKE_EXPORT_COMPILE_COMMANDS True) +set(CMAKE_LINK_WHAT_YOU_USE True) +set(WINDOWS_EXPORT_ALL_SYMBOLS True) +set(CONAN_CMAKE_SILENT_OUTPUT True) + +option(TC_ENABLE_UNIT_TESTS "Enable Unit Tests" True) +cmake_print_variables(TC_ENABLE_UNIT_TESTS) + +option(TC_ENABLE_UNIT_TESTS_COVERAGE "Enable Unit Tests Coverage" False) +cmake_print_variables(TC_ENABLE_UNIT_TESTS_COVERAGE) + +option(TC_ENABLE_BENCHMARKS "Enable Benchmarks" False) +cmake_print_variables(TC_ENABLE_BENCHMARKS) + +option(TC_ENABLE_EXAMPLES "Enable Examples" True) +cmake_print_variables(TC_ENABLE_EXAMPLES) + +option(TC_ENABLE_WARNINGS_ERROR "Enable treat Warnings as Errors" True) +cmake_print_variables(TC_ENABLE_WARNINGS_ERROR) + +option(TC_ENABLE_SANITIZER_ADDRESS "Enable Address and Leak Sanitizers" False) +cmake_print_variables(TC_ENABLE_SANITIZER_ADDRESS) + +option(TC_ENABLE_SANITIZER_THREAD "Enable Thread Sanitizer" False) +cmake_print_variables(TC_ENABLE_SANITIZER_THREAD) + +option(TC_ENABLE_CLANG_FORMAT "Enable Clang Format" False) +cmake_print_variables(TC_ENABLE_CLANG_FORMAT) + +option(TC_ENABLE_CLANG_TIDY "Enable Clang Tidy" False) +cmake_print_variables(TC_ENABLE_CLANG_TIDY) + +option(TC_ENABLE_CPPCHECK "Enable Cppcheck" False) +cmake_print_variables(TC_ENABLE_CPPCHECK) + +option(TC_ENABLE_CPPLINT "Enable Cpplint" False) +cmake_print_variables(TC_ENABLE_CPPLINT) + +function(validate_project_options) + if(TC_ENABLE_SANITIZER_ADDRESS AND TC_ENABLE_SANITIZER_THREAD) + message(FATAL_ERROR "It's not possible to set both Address and Thread sanitizers simultaneously.") + endif() + + if(TC_ENABLE_UNIT_TESTS_COVERAGE AND NOT TC_ENABLE_UNIT_TESTS) + message(FATAL_ERROR "Unit Tests must be enabled in order to run Code Coverage") + endif() + + if(TC_ENABLE_UNIT_TESTS_COVERAGE AND TC_ENABLE_BENCHMARKS) + message(FATAL_ERROR "Code Coverage cannot be enabled with Benchmarks") + endif() +endfunction() diff --git a/cmake/sanitizer_address.cmake b/cmake/sanitizer_address.cmake new file mode 100644 index 0000000..231ee13 --- /dev/null +++ b/cmake/sanitizer_address.cmake @@ -0,0 +1,15 @@ +function(add_sanitizer_address TARGET) + message(STATUS "Address sanitizer enabled") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${TARGET} PRIVATE + -g + -O1 + -fsanitize=address + -fno-omit-frame-pointer + ) + target_link_libraries(${TARGET} PRIVATE -fsanitize=address) + elseif(MSVC) + target_compile_options(${TARGET} PRIVATE -fsanitize=address) + target_link_libraries(${TARGET} PRIVATE -fsanitize=address) + endif() +endfunction() diff --git a/cmake/sanitizer_thread.cmake b/cmake/sanitizer_thread.cmake new file mode 100644 index 0000000..a9b81bb --- /dev/null +++ b/cmake/sanitizer_thread.cmake @@ -0,0 +1,22 @@ +function(add_sanitizer_thread TARGET) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + message(STATUS "Address sanitizer enabled") + target_compile_options(${TARGET} PRIVATE + -g + -O1 + -fsanitize=thread + -fno-omit-frame-pointer + ) + target_link_libraries(${TARGET} PRIVATE -fsanitize=thread) + elseif(MSVC) + message("Thread sanitizer not supported.") + endif() +endfunction() + +# NOTE: +# Thread Sanitizer is currently broken on several Linux distributions (will be fixed with clang18/Ubuntu24.04). +# This is the error that is printed when a program compiled with -fsanitize=thread is executed: +# FATAL: ThreadSanitizer: unexpected memory mapping 0x58509008b000-0x58509008d000 +# There is currently a suggested workaround outlined here: https://github.com/google/sanitizers/issues/1716 +# This is the suggested workaround that fixes Thread Sanitizer error: +# sudo sysctl vm.mmap_rnd_bits=28 diff --git a/cmake/unit_tests.cmake b/cmake/unit_tests.cmake new file mode 100644 index 0000000..b6ed3ac --- /dev/null +++ b/cmake/unit_tests.cmake @@ -0,0 +1,11 @@ +function(setup_unit_tests TARGET_NAME) + set(TARGET_NAME_UNIT_TEST ${TARGET_NAME}_unit_tests) + find_package(GTest REQUIRED) + include(GoogleTest) + add_executable(${TARGET_NAME_UNIT_TEST}) + target_sources(${TARGET_NAME_UNIT_TEST} PRIVATE ${ARGN}) + target_compile_features(${TARGET_NAME_UNIT_TEST} PUBLIC cxx_std_20) + target_link_libraries(${TARGET_NAME_UNIT_TEST} PRIVATE GTest::GTest PRIVATE ${TARGET_NAME}) + gtest_discover_tests(${TARGET_NAME_UNIT_TEST} PROPERTIES TEST_DISCOVERY_TIMEOUT 30) + install(TARGETS ${TARGET_NAME_UNIT_TEST} DESTINATION unit_tests) +endfunction() diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake new file mode 100644 index 0000000..1a4f5f8 --- /dev/null +++ b/cmake/warnings.cmake @@ -0,0 +1,17 @@ +function(add_warnings TARGET) + message(STATUS "Compiler warnings enabled") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${TARGET} PRIVATE -Wall -Wextra) + elseif(MSVC) + target_compile_options(${TARGET} PRIVATE /W4) + endif() +endfunction() + +function(add_warnings_as_errors TARGET) + message(STATUS "Treat warnings as errors enabled") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${TARGET} PRIVATE -Werror) + elseif(MSVC) + target_compile_options(${TARGET} PRIVATE /WX) + endif() +endfunction() diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..bb9b324 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# Copyright 2024 TeiaCare +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from conans import ConanFile +from conan.tools.cmake import CMake, CMakeToolchain +import re + +def get_project_version(): + with open('VERSION', encoding='utf8') as version_file: + version_regex = r'^\d+\.\d+\.\d+$' + version = version_file.read().strip() + if re.match(version_regex, version): + return version + else: + raise ValueError(f"Invalid version detected into file VERSION: {version}") + +class TeiaCareRabbitMQClient(ConanFile): + name = "teiacare_sdk" + version = get_project_version() + license = "Apache License" + author = "TeiaCare" + url = "https://github.com/TeiaCare/TeiaCareRabbitMQClient" + description = "TeiaCareRabbitMQClient is a cross-platform C++ client for RabbitMQ protocol (based on AMQP)" + topics = ("sdk") + exports = "VERSION" + exports_sources = "CMakeLists.txt", "README.md", "VERSION", "cmake/*", "sdk/CMakeLists.txt", "sdk/include/*", "sdk/src/*" + settings = "os", "compiler", "build_type", "arch" + options = {"shared": [True, False], "fPIC": [True, False]} + default_options = {"shared": False, "fPIC": True} + generators = "CMakeDeps" + + def config_options(self): + if self.settings.os == "Windows": + del self.options.fPIC + + def configure(self): + if self.options.shared: + del self.options.fPIC + + def generate(self): + tc = CMakeToolchain(self) + tc.variables["BUILD_SHARED_LIBS"] = "ON" if self.options.shared else "OFF" + tc.variables["TC_ENABLE_UNIT_TESTS"] = False + tc.variables["TC_ENABLE_UNIT_TESTS_COVERAGE"] = False + tc.variables["TC_ENABLE_BENCHMARKS"] = False + tc.variables["TC_ENABLE_EXAMPLES"] = False + tc.variables["TC_ENABLE_WARNINGS_ERROR"] = True + tc.variables["TC_ENABLE_SANITIZER_ADDRESS"] = False + tc.variables["TC_ENABLE_SANITIZER_THREAD"] = False + tc.variables["TC_ENABLE_CLANG_FORMAT"] = False + tc.variables["TC_ENABLE_CLANG_TIDY"] = False + tc.variables["TC_ENABLE_CPPCHECK"] = False + tc.variables["TC_ENABLE_CPPLINT"] = False + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + self.copy(pattern="VERSION") + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.libs = ["teiacare_sdk"] + self.cpp_info.set_property("cmake_file_name", "teiacare_sdk") + self.cpp_info.set_property("cmake_target_name", "teiacare::sdk") diff --git a/rabbitmq_client/CMakeLists.txt b/rabbitmq_client/CMakeLists.txt new file mode 100644 index 0000000..3b28d98 --- /dev/null +++ b/rabbitmq_client/CMakeLists.txt @@ -0,0 +1,83 @@ +set(TARGET_NAME teiacare_rabbitmq_client) +add_library(${TARGET_NAME}) +add_library(teiacare::rabbitmq_client ALIAS ${TARGET_NAME}) + +find_package(rabbitmq-c REQUIRED) + +configure_file( + src/version.cpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/src/version.cpp +) + +set(TARGET_HEADERS + include/teiacare/rabbitmq_client/channel.hpp + include/teiacare/rabbitmq_client/rabbitmq_client.hpp + include/teiacare/rabbitmq_client/version.hpp +) + +set(TARGET_SOURCES + src/channel.cpp + src/rabbitmq_client.cpp + src/version.cpp +) + +list(APPEND ALL_SOURCES ${TARGET_HEADERS} ${TARGET_SOURCES}) +list(TRANSFORM ALL_SOURCES_ABS_PATH PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") + +target_compile_features(${TARGET_NAME} PUBLIC cxx_std_20) +target_sources(${TARGET_NAME} PUBLIC ${TARGET_HEADERS} PRIVATE ${TARGET_SOURCES}) +target_include_directories(${TARGET_NAME} + PUBLIC + $ + $ +) +target_link_libraries(${TARGET_NAME} + PUBLIC + rabbitmq::rabbitmq-static +) + +set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${${PROJECT_NAME}_VERSION} SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}) +install(TARGETS ${TARGET_NAME}) +install(DIRECTORY include DESTINATION .) + +if(TC_ENABLE_WARNINGS_ERROR) + add_warnings(${TARGET_NAME}) + add_warnings_as_errors(${TARGET_NAME}) +endif() + +################################################################# +# Unit Tests +if(TC_ENABLE_UNIT_TESTS) + set(TEST_TARGET_NAME teiacare_rabbitmq_client_unittests) + find_package(GTest REQUIRED) + include(GoogleTest) + + set(UNIT_TESTS_SRC + tests/main.cpp + ) + + add_executable(${TEST_TARGET_NAME}) + target_sources(${TEST_TARGET_NAME} PRIVATE ${UNIT_TESTS_SRC}) + target_compile_features(${TEST_TARGET_NAME} PUBLIC cxx_std_20) + target_link_libraries(${TEST_TARGET_NAME} + PRIVATE + GTest::gtest + GTest::gmock + teiacare::rabbitmq_client + ) + + gtest_discover_tests(${TEST_TARGET_NAME}) + install(TARGETS ${TEST_TARGET_NAME} DESTINATION unit_tests) + + if(TC_ENABLE_WARNINGS_ERROR) + add_warnings(${TEST_TARGET_NAME}) + add_warnings_as_errors(${TEST_TARGET_NAME}) + endif() +endif() + +################################################################# +# Example +if(TC_ENABLE_EXAMPLES) + include(examples) + add_example(teiacare_rabbitmq_client simple_client) +endif() diff --git a/rabbitmq_client/conanfile.txt b/rabbitmq_client/conanfile.txt new file mode 100644 index 0000000..325409e --- /dev/null +++ b/rabbitmq_client/conanfile.txt @@ -0,0 +1,9 @@ +[requires] +rabbitmq-c/0.14.0 +gtest/1.15.0 + +[generators] +CMakeDeps + +[options] +rabbitmq-c:ssl=True diff --git a/rabbitmq_client/examples/simple_client.cpp b/rabbitmq_client/examples/simple_client.cpp new file mode 100644 index 0000000..0b6b213 --- /dev/null +++ b/rabbitmq_client/examples/simple_client.cpp @@ -0,0 +1,36 @@ +// Copyright 2025 TeiaCare +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +int main(int argc, char** argv) +{ + tc::rmq::rabbitmq_client client{"localhost", 5672, "guest", "guest"}; + std::shared_ptr channel = client.create_channel("Channel_1"); + + channel->declare_exchange("Exchange_1", "direct", true, false); + channel->declare_queue("Queue_1", true, false, false); + channel->bind_queue("Queue_1", "Exchange_1", "routing_key"); + + channel->publish("Exchange_1", "routing_key", "HELLO WORLD!"); + const std::string msg = channel->consume("Queue_1"); + std::cout << msg << std::endl; + + std::cout << "OK" << std::endl; + + return 0; +} diff --git a/rabbitmq_client/include/teiacare/rabbitmq_client/channel.hpp b/rabbitmq_client/include/teiacare/rabbitmq_client/channel.hpp new file mode 100644 index 0000000..52ac224 --- /dev/null +++ b/rabbitmq_client/include/teiacare/rabbitmq_client/channel.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace tc::rmq +{ +/** + * @brief Represents a RabbitMQ channel. + */ +class channel +{ +public: + /** + * @brief Constructs a RabbitMQ channel. + * @param conn A weak pointer to the RabbitMQ connection. + * @param channel_id The ID of the channel. + * @param name The name of the channel. + */ + explicit channel(std::weak_ptr conn, uint16_t channel_id, const std::string& name); + + /** + * @brief Destructor for the RabbitMQ channel. + */ + ~channel(); + + /** + * @brief Declares an exchange. + * @param exchange The name of the exchange. + * @param type The type of the exchange (e.g., "direct"). + * @param durable Whether the exchange is durable. + * @param auto_delete Whether the exchange should auto-delete. + */ + void declare_exchange(const std::string& exchange, const std::string& type, bool durable, bool auto_delete); + + /** + * @brief Declares a queue. + * @param queue The name of the queue. + * @param durable Whether the queue is durable. + * @param exclusive Whether the queue is exclusive. + * @param auto_delete Whether the queue should auto-delete. + */ + void declare_queue(const std::string& queue, bool durable, bool exclusive, bool auto_delete); + + /** + * @brief Binds a queue to an exchange. + * @param queue The name of the queue. + * @param exchange The name of the exchange. + * @param routing_key The routing key for the binding. + */ + void bind_queue(const std::string& queue, const std::string& exchange, const std::string& routing_key); + + /** + * @brief Unbinds a queue from an exchange. + * @param queue The name of the queue. + * @param exchange The name of the exchange. + * @param routing_key The routing key for the binding. + */ + void unbind_queue(const std::string& queue, const std::string& exchange, const std::string& routing_key); + + /** + * @brief Publishes a message to an exchange. + * @param exchange The name of the exchange. + * @param routing_key The routing key for the message. + * @param message The message to publish. + */ + void publish(const std::string& exchange, const std::string& routing_key, const std::string& message); + + /** + * @brief Consumes a message from a queue. + * @param queue The name of the queue. + * @return The consumed message. + */ + std::string consume(const std::string& queue); + + /** + * @brief Gets the name of the channel. + * @return The name of the channel. + */ + const std::string& get_name() const + { + return _name; + } + +private: + std::weak_ptr _connection; + uint16_t _channel_id; + const std::string _name; + + void check_amqp_reply(const std::string& context) const; + amqp_connection_state_t_* get_connection() const; +}; + +} diff --git a/rabbitmq_client/include/teiacare/rabbitmq_client/rabbitmq_client.hpp b/rabbitmq_client/include/teiacare/rabbitmq_client/rabbitmq_client.hpp new file mode 100644 index 0000000..db15560 --- /dev/null +++ b/rabbitmq_client/include/teiacare/rabbitmq_client/rabbitmq_client.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include +#include +#include +#include + +// #include +// #include + +namespace tc::rmq +{ +/** + * @brief Client for interacting with RabbitMQ. + */ +class rabbitmq_client +{ +public: + /** + * @brief Constructs a RabbitMQ client. + * @param host The hostname of the RabbitMQ server. + * @param port The port of the RabbitMQ server. + * @param user The username for authentication. + * @param password The password for authentication. + */ + explicit rabbitmq_client(const std::string& address, int port, const std::string& username, const std::string& password); + + /** + * @brief Destructor for the RabbitMQ client. + */ + ~rabbitmq_client() = default; + + /** + * @brief Creates a new channel with the specified name. + * @param name The name of the channel. + * @return A shared pointer to the created channel. + */ + std::shared_ptr create_channel(const std::string& name); + + /** + * @brief Retrieves an existing channel by name. + * @param name The name of the channel. + * @return A shared pointer to the channel. + */ + std::shared_ptr get_channel(const std::string& name) const; + +private: + std::shared_ptr connection; + std::unique_ptr socket; + std::unordered_map> channels; + static uint16_t next_channel_id; + + auto init_connection() -> decltype(connection); + auto init_socket() -> decltype(socket); + void check_amqp_reply(const std::string& context) const; +}; + +} diff --git a/rabbitmq_client/include/teiacare/rabbitmq_client/version.hpp b/rabbitmq_client/include/teiacare/rabbitmq_client/version.hpp new file mode 100644 index 0000000..bcfa15d --- /dev/null +++ b/rabbitmq_client/include/teiacare/rabbitmq_client/version.hpp @@ -0,0 +1,37 @@ +// Copyright 2025 TeiaCare +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +namespace tc::sdk::info +{ +extern const char* const name; +extern const char* const version; + +extern const char* const project_description; +extern const char* const project_url; + +extern const char* const build_type; +extern const char* const compiler_name; +extern const char* const compiler_version; + +extern const char* const cxx_flags; +extern const char* const cxx_flags_debug; +extern const char* const cxx_flags_release; +extern const char* const cxx_standard; + +extern const char* const os_name; +extern const char* const os_version; +extern const char* const os_processor; +} diff --git a/rabbitmq_client/src/channel.cpp b/rabbitmq_client/src/channel.cpp new file mode 100644 index 0000000..7c1e3fd --- /dev/null +++ b/rabbitmq_client/src/channel.cpp @@ -0,0 +1,152 @@ +#include + +#include +#include +#include + +namespace tc::rmq +{ +channel::channel(std::weak_ptr connection, uint16_t channel_id, const std::string& name) + : _connection{connection} + , _channel_id{channel_id} + , _name{name} +{ + amqp_channel_open(get_connection(), _channel_id); + check_amqp_reply("Opening channel"); +} + +channel::~channel() +{ + amqp_channel_close(get_connection(), _channel_id, AMQP_REPLY_SUCCESS); +} + +void channel::declare_exchange(const std::string& exchange, const std::string& type, bool durable, bool auto_delete) +{ + amqp_exchange_declare( + get_connection(), + _channel_id, + amqp_cstring_bytes(exchange.c_str()), + amqp_cstring_bytes(type.c_str()), + 0, + durable, + auto_delete, + 0, + amqp_empty_table); + + check_amqp_reply("Declaring exchange"); +} + +void channel::declare_queue(const std::string& queue, bool durable, bool exclusive, bool auto_delete) +{ + amqp_queue_declare( + get_connection(), + _channel_id, + amqp_cstring_bytes(queue.c_str()), + 0, + durable, + exclusive, + auto_delete, + amqp_empty_table); + + check_amqp_reply("Declaring queue"); +} + +void channel::bind_queue(const std::string& queue, const std::string& exchange, const std::string& routing_key) +{ + amqp_queue_bind( + get_connection(), + _channel_id, + amqp_cstring_bytes(queue.c_str()), + amqp_cstring_bytes(exchange.c_str()), + amqp_cstring_bytes(routing_key.c_str()), + amqp_empty_table); + + check_amqp_reply("Binding queue"); +} + +void channel::unbind_queue(const std::string& queue, const std::string& exchange, const std::string& routing_key) +{ + amqp_queue_unbind( + get_connection(), + _channel_id, + amqp_cstring_bytes(queue.c_str()), + amqp_cstring_bytes(exchange.c_str()), + amqp_cstring_bytes(routing_key.c_str()), + amqp_empty_table); + + check_amqp_reply("Unbinding queue"); +} + +void channel::publish(const std::string& exchange, const std::string& routing_key, const std::string& message) +{ + amqp_basic_properties_t props; + props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG; + props.content_type = amqp_cstring_bytes("text/plain"); + props.delivery_mode = 2; // persistent delivery mode + + amqp_basic_publish( + get_connection(), + _channel_id, + amqp_cstring_bytes(exchange.c_str()), + amqp_cstring_bytes(routing_key.c_str()), + 0, + 0, + &props, + amqp_cstring_bytes(message.c_str())); + + check_amqp_reply("Publishing"); +} + +std::string channel::consume(const std::string& queue) +{ + amqp_basic_consume( + get_connection(), + _channel_id, + amqp_cstring_bytes(queue.c_str()), + amqp_empty_bytes, + 0, + 1, + 0, + amqp_empty_table); + + check_amqp_reply("Consuming"); + + amqp_envelope_t envelope; + amqp_maybe_release_buffers(get_connection()); + + amqp_rpc_reply_t ret = amqp_consume_message( + get_connection(), + &envelope, + nullptr, + 0); + + if (ret.reply_type != AMQP_RESPONSE_NORMAL) + { + throw std::runtime_error("Failed to consume message"); + } + + std::string message(static_cast(envelope.message.body.bytes), envelope.message.body.len); + amqp_destroy_envelope(&envelope); + return message; +} + +void channel::check_amqp_reply(const std::string& context) const +{ + amqp_rpc_reply_t reply = amqp_get_rpc_reply(get_connection()); + if (reply.reply_type != AMQP_RESPONSE_NORMAL) + { + throw std::runtime_error(context + " failed: " + amqp_error_string2(reply.library_error)); + } +} + +amqp_connection_state_t_* channel::get_connection() const +{ + if (auto c = _connection.lock()) + { + return c.get(); + } + + throw std::runtime_error("Connection is no longer available"); +} + +} diff --git a/rabbitmq_client/src/rabbitmq_client.cpp b/rabbitmq_client/src/rabbitmq_client.cpp new file mode 100644 index 0000000..7215ec5 --- /dev/null +++ b/rabbitmq_client/src/rabbitmq_client.cpp @@ -0,0 +1,74 @@ +#include +#include + +#include +#include +#include + +namespace tc::rmq +{ +uint16_t rabbitmq_client::next_channel_id = 0; + +auto rabbitmq_client::init_connection() -> decltype(connection) +{ + return { + amqp_new_connection(), + [](amqp_connection_state_t_* c) { + amqp_connection_close(c, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(c); + }}; +} + +auto rabbitmq_client::init_socket() -> decltype(socket) +{ + return { + amqp_tcp_socket_new(connection.get()), + [](amqp_socket_t_*) {}}; +} + +rabbitmq_client::rabbitmq_client(const std::string& address, int port, const std::string& username, const std::string& password) + : connection{init_connection()} + , socket{init_socket()} +{ + int socket_status = amqp_socket_open(socket.get(), address.c_str(), port); + if (socket_status != amqp_status_enum_::AMQP_STATUS_OK) + { + const auto error_msg = amqp_error_string2(socket_status); + throw std::runtime_error(error_msg); + } + + amqp_login(connection.get(), "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, username.c_str(), password.c_str()); +} + +std::shared_ptr rabbitmq_client::create_channel(const std::string& name) +{ + if (channels.contains(name)) + { + throw std::runtime_error("Channel with name " + name + " already exists"); + } + + auto channel_ptr = std::make_shared(connection, ++next_channel_id, name); + channels[name] = channel_ptr; + return channel_ptr; +} + +std::shared_ptr rabbitmq_client::get_channel(const std::string& name) const +{ + if (!channels.contains(name)) + { + throw std::runtime_error("Channel with name " + name + " not found"); + } + + return channels.at(name); +} + +void rabbitmq_client::check_amqp_reply(const std::string& context) const +{ + amqp_rpc_reply_t reply = amqp_get_rpc_reply(connection.get()); + if (reply.reply_type != AMQP_RESPONSE_NORMAL) + { + throw std::runtime_error(context + " failed: " + amqp_error_string2(reply.library_error)); + } +} + +} diff --git a/rabbitmq_client/src/version.cpp.in b/rabbitmq_client/src/version.cpp.in new file mode 100644 index 0000000..7ffee75 --- /dev/null +++ b/rabbitmq_client/src/version.cpp.in @@ -0,0 +1,22 @@ +namespace tc::sdk::info +{ +extern const char* const name = "@PROJECT_NAME@"; +extern const char* const version = "@PROJECT_VERSION@"; + +extern const char* const project_description = "@PROJECT_DESCRIPTION@"; +extern const char* const project_url = "@PROJECT_HOMEPAGE_URL@"; + +extern const char* const build_type = "@CMAKE_BUILD_TYPE@"; +extern const char* const compiler_name = "@CMAKE_CXX_COMPILER_ID@"; +extern const char* const compiler_version = "@CMAKE_CXX_COMPILER_VERSION@"; + +extern const char* const cxx_flags = "@CMAKE_CXX_FLAGS@"; +extern const char* const cxx_flags_debug = "@CMAKE_CXX_FLAGS_DEBUG@"; +extern const char* const cxx_flags_release = "@CMAKE_CXX_FLAGS_RELEASE@"; +extern const char* const cxx_standard = "@CMAKE_CXX_STANDARD@"; + +extern const char* const os_name = "@CMAKE_SYSTEM_NAME@"; +extern const char* const os_version = "@CMAKE_SYSTEM_VERSION@"; +extern const char* const os_processor = "@CMAKE_SYSTEM_PROCESSOR@"; + +} diff --git a/rabbitmq_client/tests/main.cpp b/rabbitmq_client/tests/main.cpp new file mode 100644 index 0000000..e2cb0aa --- /dev/null +++ b/rabbitmq_client/tests/main.cpp @@ -0,0 +1,21 @@ +// Copyright 2025 TeiaCare +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}