diff --git a/.github/workflows/test-docs-examples.yml b/.github/workflows/test-docs-examples.yml index 05f690fb94..98edba68de 100644 --- a/.github/workflows/test-docs-examples.yml +++ b/.github/workflows/test-docs-examples.yml @@ -18,7 +18,7 @@ permissions: jobs: test_docs_examples: name: Test build examples - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -35,17 +35,49 @@ jobs: submodules: recursive - name: Install apt packages - run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt-get update && sudo apt-get install -y g++-12 libopus-dev zlib1g-dev libmpg123-dev liboggz-dev cmake libfmt-dev libopusfile-dev + run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt-get update && sudo apt-get install -y clang-20 libopus-dev ninja-build zlib1g-dev libmpg123-dev liboggz-dev cmake libfmt-dev libopusfile-dev - name: Generate CMake - run: mkdir build && cd build && cmake -DDPP_NO_VCPKG=ON -DAVX_TYPE=T_fallback -DDPP_CORO=ON -DCMAKE_BUILD_TYPE=Debug .. + run: mkdir build && cd build && cmake -G Ninja -DDPP_NO_VCPKG=ON -DDPP_MODULES=ON -DAVX_TYPE=T_fallback -DDPP_CORO=ON -DCMAKE_BUILD_TYPE=Debug .. env: - CXX: g++-12 + CXX: clang++-20 - name: Build Project - run: cd build && make -j2 && sudo make install + run: cd build && cmake --build . -j2 && sudo ninja install + env: + CXX: clang++-20 - name: Test compile examples - run: cd docpages/example_code && mkdir build && cd build && cmake .. && make -j2 + run: cd docpages/example_code && mkdir build && cd build && cmake -G Ninja -DDPP_MODULES=ON .. && cmake --build . -j2 + env: + CXX: clang++-20 + + test_docs_module_windows: + name: Test build module (Windows) + runs-on: windows-2022 + concurrency: + group: ${{ github.workflow }}-windows-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout D++ + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: recursive + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3.0.0 + + - name: Generate CMake + run: cmake -B build -G "Visual Studio 17 2022" -DDPP_NO_VCPKG=ON -DDPP_MODULES=ON -DDPP_BUILD_TEST=OFF -DAVX_TYPE=AVX0 + env: + DONT_RUN_VCPKG: true + + - name: Build module example + run: cmake --build build --target using_modules_example --config Release --parallel 2 env: - CXX: g++-12 + DONT_RUN_VCPKG: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e1f1ce451..40a098d655 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ cmake_minimum_required (VERSION 3.16) set(CMAKE_CXX_STANDARD 17) - + option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(BUILD_VOICE_SUPPORT "Build voice support" ON) option(RUN_LDCONFIG "Run ldconfig after installation" ON) @@ -33,6 +33,7 @@ option(DPP_USE_EXTERNAL_JSON "Use an external installation of nlohmann::json" OF option(DPP_USE_PCH "Use precompiled headers to speed up compilation" OFF) option(AVX_TYPE "Force AVX type for speeding up audio mixing" OFF) option(DPP_TEST_VCPKG "Force VCPKG build without VCPKG installed (for development use only!)" OFF) +option(DPP_MODULES "Support for C++20 modules (experimental)" OFF) include(CheckCXXSymbolExists) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -159,3 +160,4 @@ endif() if (NOT WIN32) target_link_libraries(dpp PRIVATE std::filesystem) endif() + diff --git a/README.md b/README.md index 0164540358..1b3fe061c1 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ D++ is a lightweight and efficient library for **Discord** written in **modern C * Stable [Windows support](https://dpp.dev/buildwindows.html) * Ready-made compiled packages for Windows, Raspberry Pi (ARM64/ARM7/ARMv6), Debian x86/x64, and RPM based distributions * Highly scalable for large amounts of guilds and users +* Experimental support for [C++ modules](https://dpp.dev/using_modules.html) Want to help? Drop me a line or send a PR. diff --git a/cmake/CPackSetup.cmake b/cmake/CPackSetup.cmake index d02de875d8..a21fd8d469 100644 --- a/cmake/CPackSetup.cmake +++ b/cmake/CPackSetup.cmake @@ -11,20 +11,40 @@ set(DPP_INSTALL_LIBRARY_DIR ${CMAKE_INSTALL_LIBDIR}/${DPP_VERSIONED}) ## Pack the binary output if (WIN32) - install(TARGETS dpp - EXPORT ${DPP_EXPORT_NAME} - LIBRARY DESTINATION ${DPP_INSTALL_LIBRARY_DIR} - ARCHIVE DESTINATION ${DPP_INSTALL_LIBRARY_DIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - INCLUDES DESTINATION ${DPP_INSTALL_INCLUDE_DIR}) + if (DPP_MODULES) + install(TARGETS dpp + EXPORT ${DPP_EXPORT_NAME} + LIBRARY DESTINATION ${DPP_INSTALL_LIBRARY_DIR} + ARCHIVE DESTINATION ${DPP_INSTALL_LIBRARY_DIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${DPP_INSTALL_INCLUDE_DIR} + FILE_SET CXX_MODULES DESTINATION ${DPP_INSTALL_INCLUDE_DIR}) + else() + install(TARGETS dpp + EXPORT ${DPP_EXPORT_NAME} + LIBRARY DESTINATION ${DPP_INSTALL_LIBRARY_DIR} + ARCHIVE DESTINATION ${DPP_INSTALL_LIBRARY_DIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${DPP_INSTALL_INCLUDE_DIR}) + endif() install(DIRECTORY "${DPP_ROOT_PATH}/include/" DESTINATION "${DPP_INSTALL_INCLUDE_DIR}") else() - install(TARGETS dpp - EXPORT ${DPP_EXPORT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + if (DPP_MODULES) + install(TARGETS dpp + EXPORT ${DPP_EXPORT_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + else() + install(TARGETS dpp + EXPORT ${DPP_EXPORT_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + endif() endif() ## Allow for a specific version to be chosen in the `find_package` command diff --git a/docpages/example_code/CMakeLists.txt b/docpages/example_code/CMakeLists.txt index 777d881e41..98e8069ad9 100644 --- a/docpages/example_code/CMakeLists.txt +++ b/docpages/example_code/CMakeLists.txt @@ -28,14 +28,25 @@ # libmpg123-dev # dpp latest master with -DDPP_CORO=ON installed sytemwide -cmake_minimum_required (VERSION 3.16) +cmake_minimum_required (VERSION 3.28) project(documentation_tests) include("${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/colour.cmake") -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDPP_CORO -std=c++20 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Wextra -Wpedantic -Werror -Wno-unused-parameter -Wno-deprecated-declarations") +option(DPP_MODULES "Build examples with C++20 modules support" ON) + +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDPP_CORO -std=c++20 -pthread -O0 -fPIC -DFMT_HEADER_ONLY -Wall -Wextra -Wpedantic -Werror -Wno-unused-parameter -Wno-deprecated-declarations") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") +set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic") + +# Create gcm.cache directory and symlink to the main build's dpp.gcm +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/gcm.cache") + file(CREATE_LINK "${CMAKE_SOURCE_DIR}/../../build/library/CMakeFiles/dpp.dir/dpp.gcm" + "${CMAKE_BINARY_DIR}/gcm.cache/dpp.gcm" SYMBOLIC) +endif() + file(GLOB example_list ./*.cpp) foreach (example ${example_list}) get_filename_component(examplename ${example} NAME) @@ -43,4 +54,16 @@ foreach (example ${example_list}) add_executable(${examplename}_out ${example}) target_link_libraries(${examplename}_out dl dpp mpg123 oggz ogg opusfile opus) include_directories(/usr/include/opus) + + target_compile_definitions(${examplename}_out PRIVATE DPP_MODULES) + set_target_properties(${examplename}_out PROPERTIES CXX_SCAN_FOR_MODULES ON) + + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(${examplename}_out PRIVATE -fmodules-ts) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(${examplename}_out PRIVATE + -fmodules + -fprebuilt-module-path=${CMAKE_SOURCE_DIR}/../../build/library/CMakeFiles/dpp.dir + ) + endif() endforeach(example) diff --git a/docpages/example_code/using_modules.cpp b/docpages/example_code/using_modules.cpp new file mode 100644 index 0000000000..4c26f231da --- /dev/null +++ b/docpages/example_code/using_modules.cpp @@ -0,0 +1,24 @@ +#include + +import dpp; + +int main() { + dpp::cluster bot("YOUR_BOT_TOKEN_HERE"); + + bot.on_slashcommand([](const dpp::slashcommand_t& event) -> void { + if (event.command.get_command_name() == "ping") { + event.reply("Pong!"); + } + }); + + bot.on_ready([&bot](const dpp::ready_t& event) -> void { + if (dpp::run_once()) { + bot.global_command_create( + dpp::slashcommand("ping", "Ping pong!", bot.me.id) + ); + } + }); + + bot.start(dpp::start_type::st_wait); + return 0; +} diff --git a/docpages/example_programs/misc.md b/docpages/example_programs/misc.md index f8a1cc3578..848a886d3c 100644 --- a/docpages/example_programs/misc.md +++ b/docpages/example_programs/misc.md @@ -13,3 +13,4 @@ This section lists examples that do not fit neatly into any of the categories ab * \subpage setting_status * \subpage using-emojis * \subpage using_timers +* \subpage using_modules diff --git a/docpages/example_programs/misc/using_modules.md b/docpages/example_programs/misc/using_modules.md new file mode 100644 index 0000000000..9403a288b3 --- /dev/null +++ b/docpages/example_programs/misc/using_modules.md @@ -0,0 +1,11 @@ +\page using_modules Using D++ with C++ modules + +\include{doc} modules_warn.dox + +D++ is offered as a C++ module, which offers improved compile times over a traditional header. + +In order to enable support, you must use C++20 (or later) with any module-supporting compiler. To activate the feature, pass the `DPP_MODULES` flag to CMake. Ensure that the generated build system supports modules (for CMake, this is usually Ninja; note CMake does not currently support modules with Makefile). + +Once this is done, simply `import dpp;` and we're good to go! + +\include{cpp} using_modules.cpp diff --git a/docpages/include/coro_warn.dox b/docpages/include/coro_warn.dox index beeb5ad1c7..9ddcbdf90f 100644 --- a/docpages/include/coro_warn.dox +++ b/docpages/include/coro_warn.dox @@ -1 +1 @@ -\warning D++ Coroutines are currently only supported by D++ on g++ 13, clang/LLVM 14, and MSVC 19.37 or above. Additionally, your program has to support C++20. +\warning D++ coroutines are currently only supported by D++ on g++ 13, clang/LLVM 14, and MSVC 19.37 or above. Additionally, your program has to support C++20. diff --git a/docpages/include/modules_warn.dox b/docpages/include/modules_warn.dox new file mode 100644 index 0000000000..b131a84f15 --- /dev/null +++ b/docpages/include/modules_warn.dox @@ -0,0 +1 @@ +\warning D++ modules are currently only supported by D++ on g++ 15, clang/LLVM 17, and MSVC 17.6 or above. Additionally, your program has to support C++20. diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 7c631a2b76..4053896da3 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -58,7 +58,7 @@ namespace dpp { * requests to Discord. This is useful for bots that do not need to receive websocket events as it will save a lot of * resources. */ -constexpr uint32_t NO_SHARDS = ~0U; +inline constexpr uint32_t NO_SHARDS = ~0U; /** * @brief Types of startup for cluster::start() diff --git a/include/dpp/colors.h b/include/dpp/colors.h index bfc0688f5b..ec98fa3513 100644 --- a/include/dpp/colors.h +++ b/include/dpp/colors.h @@ -31,7 +31,7 @@ namespace dpp { * @brief predefined color constants. */ namespace colors { - static constexpr uint32_t + inline constexpr uint32_t white = 0xFFFFFF, discord_white = 0xFFFFFE, light_gray = 0xC0C0C0, diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index 7cd04b848d..cdbaa84239 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -57,7 +57,7 @@ class cluster; * @brief How many seconds to wait between (re)connections. DO NOT change this. * It is mandated by the Discord API spec! */ -constexpr time_t RECONNECT_INTERVAL = 5; +inline constexpr time_t RECONNECT_INTERVAL = 5; /** * @brief Represents different event opcodes sent and received on a shard websocket diff --git a/include/dpp/dpp.cppm b/include/dpp/dpp.cppm new file mode 100644 index 0000000000..3d7ccb4062 --- /dev/null +++ b/include/dpp/dpp.cppm @@ -0,0 +1,67 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * 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. + * + ************************************************************************************/ + +module; + +// Include the entire standard library so it doesn't cause issue in our headers +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +export module dpp; + +export extern "C++" { + #include + #include + #include + #include +} diff --git a/include/dpp/etf.h b/include/dpp/etf.h index f4e9f1d29b..b51f7452bc 100644 --- a/include/dpp/etf.h +++ b/include/dpp/etf.h @@ -42,7 +42,7 @@ namespace dpp { /** * @brief Current ETF format version in use */ -const uint8_t FORMAT_VERSION = 131; +inline constexpr uint8_t FORMAT_VERSION = 131; /** * @brief Represents a token which identifies the type of value which follows it diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h index 95e52db459..96b661e337 100644 --- a/include/dpp/httpsclient.h +++ b/include/dpp/httpsclient.h @@ -32,10 +32,10 @@ namespace dpp { -static inline const std::string http_version = "DiscordBot (https://github.com/brainboxdotcc/DPP, " +inline const std::string http_version = "DiscordBot (https://github.com/brainboxdotcc/DPP, " + to_hex(DPP_VERSION_MAJOR, false) + "." + to_hex(DPP_VERSION_MINOR, false) + "." + to_hex(DPP_VERSION_PATCH, false) + ")"; -static inline constexpr const char* DISCORD_HOST = "https://discord.com"; +inline constexpr const char* DISCORD_HOST = "https://discord.com"; /** * @brief HTTP connection status diff --git a/include/dpp/message.h b/include/dpp/message.h index 37926cbd2c..06974b61e2 100644 --- a/include/dpp/message.h +++ b/include/dpp/message.h @@ -2010,37 +2010,37 @@ namespace embed_type { /** * @brief Rich text */ - const std::string emt_rich = "rich"; + inline const std::string emt_rich = "rich"; /** * @brief Image */ - const std::string emt_image = "image"; + inline const std::string emt_image = "image"; /** * @brief Video link */ - const std::string emt_video = "video"; + inline const std::string emt_video = "video"; /** * @brief Animated gif */ - const std::string emt_gifv = "gifv"; + inline const std::string emt_gifv = "gifv"; /** * @brief Article */ - const std::string emt_article = "article"; + inline const std::string emt_article = "article"; /** * @brief Link URL */ - const std::string emt_link = "link"; + inline const std::string emt_link = "link"; /** * @brief Auto moderation filter */ - const std::string emt_automod = "auto_moderation_message"; + inline const std::string emt_automod = "auto_moderation_message"; } // namespace embed_type /** diff --git a/include/dpp/sslconnection.h b/include/dpp/sslconnection.h index 2f69426332..53198b1389 100644 --- a/include/dpp/sslconnection.h +++ b/include/dpp/sslconnection.h @@ -63,17 +63,17 @@ DPP_EXPORT bool set_nonblocking(dpp::socket sockfd, bool non_blocking); * SSL_read in non-blocking mode will only read 16k at a time. There's no point in a bigger buffer as * it'd go unused. */ -constexpr uint16_t DPP_BUFSIZE{16 * 1024}; +inline constexpr uint16_t DPP_BUFSIZE{16 * 1024}; /** * @brief Represents a failed socket system call, e.g. connect() failure */ -constexpr int ERROR_STATUS{-1}; +inline constexpr int ERROR_STATUS{-1}; /** * @brief Maximum number of internal connect() retries on TCP connections */ -constexpr int MAX_RETRIES{4}; +inline constexpr int MAX_RETRIES{4}; /** * @brief Implements a simple non-blocking SSL stream connection. diff --git a/include/dpp/user.h b/include/dpp/user.h index 59c196178b..248834013a 100644 --- a/include/dpp/user.h +++ b/include/dpp/user.h @@ -28,7 +28,7 @@ namespace dpp { -constexpr uint32_t MAX_AVATAR_SIZE = 10240 * 1000; // 10240KB. +inline constexpr uint32_t MAX_AVATAR_SIZE = 10240 * 1000; // 10240KB. /** * @brief Various bitmask flags used to represent information about a dpp::user diff --git a/include/dpp/zlibcontext.h b/include/dpp/zlibcontext.h index e3396cd348..4232c160de 100644 --- a/include/dpp/zlibcontext.h +++ b/include/dpp/zlibcontext.h @@ -35,7 +35,7 @@ namespace dpp { /** * @brief Size of decompression buffer for zlib compressed traffic */ -constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; +inline constexpr size_t DECOMP_BUFFER_SIZE = 512 * 1024; /** * @brief This is an opaque class containing zlib library specific structures. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index bfb41e892d..f4eff3a5ae 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -330,20 +330,20 @@ if (HAVE_VOICE) # Private statically linked dependencies if(NOT BUILD_SHARED_LIBS AND NOT APPLE) target_link_libraries(dpp PRIVATE - mlspp.a - mls_vectors.a - bytes.a - tls_syntax.a - hpke.a + $ + $ + $ + $ + $ ) message("-- INFO: Linking static dependencies") else() target_link_libraries(dpp PRIVATE - mlspp - mls_vectors - bytes - tls_syntax - hpke + $ + $ + $ + $ + $ ) message("-- INFO: Linking dynamic dependencies") endif() @@ -465,17 +465,35 @@ if (NOT BUILD_SHARED_LIBS) target_compile_definitions(dppstatic PUBLIC $) endif() +if (DPP_MODULES) + message("-- ${Green}Modules are enabled!${ColourReset}") + + set_target_properties(dpp PROPERTIES CXX_EXTENSIONS OFF) + + # Add module file set directly to the dpp target + target_sources(dpp + PUBLIC + FILE_SET CXX_MODULES TYPE CXX_MODULES + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../include" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/../include/dpp/dpp.cppm" + ) + + target_compile_definitions(dpp PUBLIC DPP_MODULES) +else() + message("-- ${Yellow}Modules are disabled.${ColourReset}") +endif() + if (DPP_BUILD_TEST) enable_testing(${CMAKE_CURRENT_SOURCE_DIR}/..) file(GLOB testnamelist "${CMAKE_CURRENT_SOURCE_DIR}/../src/*") foreach (fulltestname ${testnamelist}) get_filename_component(testname ${fulltestname} NAME) - if (NOT "${testname}" STREQUAL "dpp") + if (NOT "${testname}" STREQUAL "dpp" AND NOT "${testname}" STREQUAL "modules") message("-- Configuring test: ${Green}${testname}${ColourReset} with source: ${modules_dir}/${testname}/*.cpp") set (testsrc "") file(GLOB testsrc "${modules_dir}/${testname}/*.cpp") add_executable(${testname} ${testsrc}) - if ((NOT DPP_NO_CORO) OR DPP_FORMATTERS) + if ((NOT DPP_NO_CORO) OR DPP_FORMATTERS OR DPP_MODULES) target_compile_features(${testname} PRIVATE cxx_std_20) else() target_compile_features(${testname} PRIVATE cxx_std_17) @@ -483,7 +501,15 @@ if (DPP_BUILD_TEST) if (MSVC) target_compile_options(${testname} PRIVATE /utf-8) endif() - if(BUILD_SHARED_LIBS) + if (DPP_MODULES) + target_link_libraries(${testname} PUBLIC dpp) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(${testname} PRIVATE -fmodules-ts) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(${testname} PRIVATE -fmodules) + endif() + set_target_properties(${testname} PROPERTIES CXX_SCAN_FOR_MODULES ON CXX_EXTENSIONS OFF) + elseif(BUILD_SHARED_LIBS) target_link_libraries(${testname} PUBLIC ${modname}) else() target_link_libraries(${testname} PUBLIC dppstatic) @@ -517,7 +543,6 @@ if(DPP_INSTALL) # Installation include(GNUInstallDirs) - install(TARGETS dpp LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) message("Library install directory at ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") install(DIRECTORY ../include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) message("Include files install directory at ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}") diff --git a/src/unittest/module_test.cpp b/src/unittest/module_test.cpp new file mode 100644 index 0000000000..d235b59e7a --- /dev/null +++ b/src/unittest/module_test.cpp @@ -0,0 +1,141 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * 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. + * + ************************************************************************************/ + +#ifdef DPP_MODULES +#include "test.h" + +import dpp; + +/** + * @brief Test basic dpp types and functionality via module import + */ +void test_dpp_module_basic() { + start_test(MODULE_IMPORT_BASIC); + + dpp::snowflake test_id = 825411104208977952ULL; + time_t extracted_timestamp = test_id.get_creation_time(); + if (extracted_timestamp != 1616978723) { + set_status(MODULE_IMPORT_BASIC, ts_failed, "snowflake timestamp extraction"); + return; + } + + // User object creation test + dpp::user test_user; + test_user.id = 987654321; + test_user.username = "ModuleTestUser"; + if (test_user.id != 987654321 || test_user.username != "ModuleTestUser") { + set_status(MODULE_IMPORT_BASIC, ts_failed, "user object creation"); + return; + } + + // Testing ts_to_string + std::string test_time = dpp::ts_to_string(1642611864); + if (test_time != "2022-01-19T17:04:24Z") { + set_status(MODULE_IMPORT_BASIC, ts_failed, "timestamp conversion"); + return; + } + + // Message object creation test + dpp::message test_msg; + test_msg.content = "Test message from module"; + test_msg.id = 111222333; + + auto is_valid_message = [](const dpp::message& msg, std::string_view s, int test_id) -> bool { + return msg.content == s && msg.id == test_id; + }; + + if (!is_valid_message(test_msg, "Test message from module", 111222333)) { + set_status(MODULE_IMPORT_BASIC, ts_failed, "message object creation"); + return; + } + + // URL encoding test + std::string encoded = dpp::utility::url_encode("test value"); + if (encoded != "test%20value") { + set_status(MODULE_IMPORT_BASIC, ts_failed, "URL encoding"); + return; + } + + // Markdown escaping test + std::string markdown = "**bold** _italic_"; + std::string escaped = dpp::utility::markdown_escape(markdown); + if (escaped != "\\*\\*bold\\*\\* \\_italic\\_") { + set_status(MODULE_IMPORT_BASIC, ts_failed, "markdown escaping"); + return; + } + + // Role comparison test + dpp::role r1; + dpp::role r2; + r1.id = 100; + r1.position = 1; + r1.guild_id = 500; + r2.id = 200; + r2.position = 2; + r2.guild_id = 500; + + auto role_comparison_1 = [](const dpp::role& r1, const dpp::role& r2) -> bool { + return (r1 < r2); + }; + + auto role_comparison_2 = [](const dpp::role& r1, const dpp::role& r2) -> bool { + return !(r1 > r2); + }; + + if (!role_comparison_1(r1, r2) || !role_comparison_2(r1, r2)) { + set_status(MODULE_IMPORT_BASIC, ts_failed, "role comparison"); + return; + } + + set_status(MODULE_IMPORT_BASIC, ts_success); +} + +/** + * @brief Test dpp module coroutine support (if enabled) + */ +void test_dpp_module_coro() { + start_test(MODULE_IMPORT_CORO); + +#ifndef DPP_NO_CORO + dpp::promise test_promise; + test_promise.set_value(42); + + if constexpr (!requires { dpp::task{}; }) { + set_status(MODULE_IMPORT_CORO, ts_failed, "coroutine types not accessible"); + return; + } + + set_status(MODULE_IMPORT_CORO, ts_success); +#else + set_status(MODULE_IMPORT_CORO, ts_skipped, "coroutines disabled"); +#endif +} + +/** + * @brief Main entry point for module tests + */ +void run_module_tests() { + test_dpp_module_basic(); + test_dpp_module_coro(); +} + +#endif diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 3fbb0c961f..da00bc13b4 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -334,6 +334,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(WEBHOOK, false); } + // Test C++ module import + #ifdef DPP_MODULES + run_module_tests(); + #endif + { // test dpp::snowflake start_test(SNOWFLAKE); bool success = true; diff --git a/src/unittest/test.h b/src/unittest/test.h index eba614b7c0..01f7820b5e 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -157,6 +157,11 @@ DPP_TEST(FORUM_CHANNEL_DELETE, "delete the created forum channel", tf_online); DPP_TEST(ERRORS, "Human readable error translation", tf_offline); DPP_TEST(INVALIDUTF8, "Invalid UTF-8 handling", tf_online); +#ifdef DPP_MODULES +DPP_TEST(MODULE_IMPORT_BASIC, "C++ module import: basic types", tf_offline); +DPP_TEST(MODULE_IMPORT_CORO, "C++ module import: coroutines", tf_offline); +#endif + DPP_TEST(GUILD_EDIT, "cluster::guild_edit", tf_online); DPP_TEST(GUILD_BAN_CREATE, "cluster::guild_ban_add ban three well-known discord accounts", tf_online); DPP_TEST(GUILD_BAN_GET, "cluster::guild_get_ban getting one of the banned accounts", tf_online); @@ -570,6 +575,13 @@ inline constexpr auto is_owner = [](auto &&user) noexcept { return get_user_snowflake(user) == TEST_USER_ID; }; +#ifdef DPP_MODULES +/** + * @brief Main entry point for module tests + */ +void run_module_tests(); +#endif + #define DPP_RUNTIME_CHECK(test, check, var) if (!check) { var = false; set_status(test, ts_failed, "check failed: " #check); } #define DPP_COMPILETIME_CHECK(test, check, var) static_assert(check, #test ": " #check)