diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf40f8e..74ede44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,3 +59,90 @@ jobs: - name: Run benchmark if: matrix.build_type == 'Release' run: ./build/examples/benchmark + + package-consumer-check: + runs-on: ubuntu-24.04 + name: package-consumer-check + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + cmake \ + ninja-build \ + liburing-dev \ + libssl-dev \ + libnghttp2-dev \ + g++-14 + + - name: Configure CMake (installable build) + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=gcc-14 \ + -DCMAKE_CXX_COMPILER=g++-14 \ + -DELIO_BUILD_TESTS=OFF \ + -DELIO_BUILD_EXAMPLES=OFF \ + -DELIO_ENABLE_TLS=ON \ + -DELIO_ENABLE_HTTP=ON \ + -DELIO_ENABLE_HTTP2=ON + + - name: Build and install + run: | + cmake --build build --parallel + cmake --install build --prefix ${{ github.workspace }}/_install + + - name: Verify downstream consumption + run: | + mkdir -p consumer + cat > consumer/CMakeLists.txt << 'EOF' + cmake_minimum_required(VERSION 3.20) + project(ElioConsumer LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 20) + + find_package(Elio REQUIRED) + + add_executable(consumer main.cpp) + target_link_libraries(consumer PRIVATE Elio::elio) + + if(TARGET Elio::elio_tls) + target_link_libraries(consumer PRIVATE Elio::elio_tls) + target_compile_definitions(consumer PRIVATE ELIO_HAS_TLS_TARGET=1) + endif() + + if(TARGET Elio::elio_http) + target_link_libraries(consumer PRIVATE Elio::elio_http) + target_compile_definitions(consumer PRIVATE ELIO_HAS_HTTP_TARGET=1) + endif() + + if(TARGET Elio::elio_http2) + target_link_libraries(consumer PRIVATE Elio::elio_http2) + target_compile_definitions(consumer PRIVATE ELIO_HAS_HTTP2_TARGET=1) + endif() + EOF + + cat > consumer/main.cpp << 'EOF' + #include + #if defined(ELIO_HAS_TLS_TARGET) + #include + #endif + #if defined(ELIO_HAS_HTTP_TARGET) + #include + #endif + #if defined(ELIO_HAS_HTTP2_TARGET) + #include + #endif + + int main() { + return 0; + } + EOF + + cmake -S consumer -B consumer/build -G Ninja \ + -DCMAKE_CXX_COMPILER=g++-14 \ + -DCMAKE_PREFIX_PATH=${{ github.workspace }}/_install + cmake --build consumer/build --parallel diff --git a/CMakeLists.txt b/CMakeLists.txt index d52ce87..d654e56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.20) project(Elio VERSION 0.3.0 LANGUAGES CXX) +include(CMakePackageConfigHelpers) + # Determine if Elio is the top-level project or included via add_subdirectory/FetchContent if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) set(ELIO_IS_TOP_LEVEL TRUE) @@ -14,6 +16,8 @@ option(ELIO_BUILD_EXAMPLES "Build Elio examples" ${ELIO_IS_TOP_LEVEL}) option(ELIO_ENABLE_TLS "Enable TLS support (requires OpenSSL)" ${ELIO_IS_TOP_LEVEL}) option(ELIO_ENABLE_HTTP "Enable HTTP client/server support (requires TLS)" ${ELIO_IS_TOP_LEVEL}) option(ELIO_ENABLE_HTTP2 "Enable HTTP/2 support (requires nghttp2)" ${ELIO_IS_TOP_LEVEL}) +option(ELIO_ENABLE_DEVELOPER_WARNINGS "Enable strict warning flags for Elio tests/examples" ${ELIO_IS_TOP_LEVEL}) +option(ELIO_WARNINGS_AS_ERRORS "Treat warnings as errors for Elio tests/examples" ${ELIO_IS_TOP_LEVEL}) # Platform check - Linux only if(NOT UNIX OR APPLE) @@ -35,14 +39,6 @@ target_include_directories(elio INTERFACE $ ) -# Compiler flags for our code only (not dependencies) -target_compile_options(elio INTERFACE - -Wall - -Wextra - -Werror - -Wpedantic -) - # Dependencies via FetchContent include(FetchContent) @@ -70,7 +66,7 @@ if(URING_LIBRARY AND URING_INCLUDE_DIR) message(STATUS "Found liburing: ${URING_LIBRARY}") target_compile_definitions(elio INTERFACE ELIO_HAS_IO_URING=1) target_include_directories(elio INTERFACE ${URING_INCLUDE_DIR}) - target_link_libraries(elio INTERFACE fmt::fmt pthread ${URING_LIBRARY}) + target_link_libraries(elio INTERFACE fmt::fmt pthread uring) else() message(STATUS "liburing not found, io_uring backend will be disabled") target_compile_definitions(elio INTERFACE ELIO_HAS_IO_URING=0) @@ -139,7 +135,11 @@ if(ELIO_ENABLE_HTTP AND TARGET elio_tls) $ $ ) - target_link_libraries(elio_http2 INTERFACE elio_http nghttp2_static) + target_link_libraries(elio_http2 INTERFACE + elio_http + $ + $ + ) target_compile_definitions(elio_http2 INTERFACE ELIO_HAS_HTTP2=1) message(STATUS "HTTP/2 support enabled via nghttp2") endif() @@ -162,13 +162,53 @@ endif() # Installation install(DIRECTORY include/ DESTINATION include) -install(TARGETS elio EXPORT ElioTargets) + +set(ELIO_EXPORT_TARGETS elio) +if(TARGET elio_tls) + list(APPEND ELIO_EXPORT_TARGETS elio_tls) +endif() +if(TARGET elio_http) + list(APPEND ELIO_EXPORT_TARGETS elio_http) +endif() +if(TARGET elio_http2) + list(APPEND ELIO_EXPORT_TARGETS elio_http2) +endif() + +set(ELIO_PACKAGE_NEEDS_OPENSSL OFF) +if(TARGET elio_tls) + set(ELIO_PACKAGE_NEEDS_OPENSSL ON) +endif() + +set(ELIO_PACKAGE_NEEDS_NGHTTP2 OFF) +if(TARGET elio_http2) + set(ELIO_PACKAGE_NEEDS_NGHTTP2 ON) +endif() + +install(TARGETS ${ELIO_EXPORT_TARGETS} EXPORT ElioTargets) install(EXPORT ElioTargets FILE ElioTargets.cmake NAMESPACE Elio:: DESTINATION lib/cmake/Elio ) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ElioConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/ElioConfig.cmake + INSTALL_DESTINATION lib/cmake/Elio +) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/ElioConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/ElioConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/ElioConfigVersion.cmake + DESTINATION lib/cmake/Elio +) + # Install debug tools install(PROGRAMS tools/elio-pstack diff --git a/README.md b/README.md index 94d5edc..d10aefc 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,37 @@ cmake .. cmake --build . ``` +### CMake Options + +```bash +# Core toggles +cmake .. -DELIO_BUILD_TESTS=ON -DELIO_BUILD_EXAMPLES=ON +cmake .. -DELIO_ENABLE_TLS=ON -DELIO_ENABLE_HTTP=ON -DELIO_ENABLE_HTTP2=ON + +# Warning policy for repository-local targets (tests/examples only) +cmake .. -DELIO_ENABLE_DEVELOPER_WARNINGS=ON -DELIO_WARNINGS_AS_ERRORS=ON +``` + +Note: strict warning flags are applied only to Elio's tests/examples targets and are not propagated through exported interface targets. + +### Install And Use As A Package + +```bash +cmake --install build --prefix /your/prefix +``` + +Then in another CMake project: + +```cmake +find_package(Elio REQUIRED) +target_link_libraries(your_target PRIVATE Elio::elio) + +# Optional targets when enabled during Elio build +# Elio::elio_tls +# Elio::elio_http +# Elio::elio_http2 +``` + ### Running Tests ```bash diff --git a/cmake/ElioConfig.cmake.in b/cmake/ElioConfig.cmake.in new file mode 100644 index 0000000..b779f24 --- /dev/null +++ b/cmake/ElioConfig.cmake.in @@ -0,0 +1,18 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +find_dependency(fmt REQUIRED) + +if(@ELIO_PACKAGE_NEEDS_OPENSSL@) + find_dependency(OpenSSL REQUIRED) +endif() + +if(@ELIO_PACKAGE_NEEDS_NGHTTP2@) + find_library(ELIO_NGHTTP2_LIBRARY nghttp2) + if(NOT ELIO_NGHTTP2_LIBRARY) + set(ELIO_NGHTTP2_LIBRARY nghttp2) + endif() +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/ElioTargets.cmake) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2e53f45..10e36e0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,12 @@ # Example programs +if(ELIO_ENABLE_DEVELOPER_WARNINGS) + add_compile_options(-Wall -Wextra -Wpedantic) + if(ELIO_WARNINGS_AS_ERRORS) + add_compile_options(-Werror) + endif() +endif() + # Static linking for libgcc and libstdc++ for portability set(STATIC_LINK_FLAGS -static-libgcc -static-libstdc++) diff --git a/include/elio/http/websocket_client.hpp b/include/elio/http/websocket_client.hpp index 77d68b0..89d180f 100644 --- a/include/elio/http/websocket_client.hpp +++ b/include/elio/http/websocket_client.hpp @@ -175,8 +175,11 @@ class ws_client { // Process control frames while (parser_.has_control_frame()) { - auto [op, payload] = *parser_.get_control_frame(); - co_await handle_control_frame(op, payload); + auto control_frame = parser_.get_control_frame(); + if (!control_frame) { + break; + } + co_await handle_control_frame(control_frame->first, control_frame->second); } // Check for errors @@ -459,7 +462,9 @@ class ws_client { break; case opcode::close: { - auto [code, reason] = parse_close_payload(payload); + auto close_info = parse_close_payload(payload); + auto code = close_info.first; + auto& reason = close_info.second; ELIO_LOG_DEBUG("WebSocket close received: {} {}", static_cast(code), reason); diff --git a/include/elio/http/websocket_server.hpp b/include/elio/http/websocket_server.hpp index 736e9ef..b0b88bd 100644 --- a/include/elio/http/websocket_server.hpp +++ b/include/elio/http/websocket_server.hpp @@ -154,8 +154,11 @@ class ws_connection { // Process control frames while (parser_.has_control_frame()) { - auto [op, payload] = *parser_.get_control_frame(); - co_await handle_control_frame(op, payload); + auto control_frame = parser_.get_control_frame(); + if (!control_frame) { + break; + } + co_await handle_control_frame(control_frame->first, control_frame->second); } // Check for errors @@ -251,7 +254,9 @@ class ws_connection { break; case opcode::close: { - auto [code, reason] = parse_close_payload(payload); + auto close_info = parse_close_payload(payload); + auto code = close_info.first; + auto& reason = close_info.second; ELIO_LOG_DEBUG("WebSocket close received: {} {}", static_cast(code), reason); diff --git a/include/elio/rpc/rpc_client.hpp b/include/elio/rpc/rpc_client.hpp index 2c0ebfc..dcc0a72 100644 --- a/include/elio/rpc/rpc_client.hpp +++ b/include/elio/rpc/rpc_client.hpp @@ -231,13 +231,13 @@ class rpc_client : public std::enable_shared_from_this> { // Build and send request auto timeout_ms = static_cast( std::chrono::duration_cast(timeout).count()); - auto [header, payload] = build_request(request_id, Method::id, request, timeout_ms); + auto request_frame = build_request(request_id, Method::id, request, timeout_ms); { co_await send_mutex_.lock(); sync::lock_guard send_guard(send_mutex_); - bool sent = co_await write_frame(stream_, header, payload); + bool sent = co_await write_frame(stream_, request_frame.first, request_frame.second); if (!sent) { std::lock_guard lock(pending_mutex_); pending_requests_.erase(request_id); @@ -314,12 +314,12 @@ class rpc_client : public std::enable_shared_from_this> { } uint32_t request_id = id_generator_.next(); - auto [header, payload] = build_request(request_id, Method::id, request); + auto request_frame = build_request(request_id, Method::id, request); co_await send_mutex_.lock(); sync::lock_guard send_guard(send_mutex_); - co_return co_await write_frame(stream_, header, payload); + co_return co_await write_frame(stream_, request_frame.first, request_frame.second); } /// Send a ping and wait for pong diff --git a/include/elio/rpc/rpc_server.hpp b/include/elio/rpc/rpc_server.hpp index 88a9a22..0f3282e 100644 --- a/include/elio/rpc/rpc_server.hpp +++ b/include/elio/rpc/rpc_server.hpp @@ -161,12 +161,12 @@ class rpc_session : public std::enable_shared_from_this> { auto it = handlers_.find(header.method_id); if (it == handlers_.end()) { ELIO_LOG_WARNING("RPC session: method {} not found", header.method_id); - auto [err_header, err_payload] = build_error_response( + auto error_frame = build_error_response( header.request_id, rpc_error::method_not_found, "Method not found" ); - co_await send_response(err_header, err_payload); + co_await send_response(error_frame.first, error_frame.second); co_return; } @@ -218,12 +218,12 @@ class rpc_session : public std::enable_shared_from_this> { co_await send_response(resp_header, response_payload); } else { - auto [err_header, err_payload] = build_error_response( + auto error_frame = build_error_response( header.request_id, error_code, error_message ); - co_await send_response(err_header, err_payload); + co_await send_response(error_frame.first, error_frame.second); } // Invoke cleanup callback after response is sent diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1d65100..9922f40 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,11 @@ # Test sources +if(ELIO_ENABLE_DEVELOPER_WARNINGS) + add_compile_options(-Wall -Wextra -Wpedantic) + if(ELIO_WARNINGS_AS_ERRORS) + add_compile_options(-Werror) + endif() +endif() + set(TEST_SOURCES test_main.cpp unit/test_logger.cpp