HIGH: - H1: nlohmann::json::exception wrapped as ProtocolError at 5 sites in client.cpp via with_protocol_guard helper. Preserves the documented clawdforge::Error catch-all base contract; nlohmann types never leak into the message (e.what() only). - H2: libcurl MAXREDIRS=5, REDIR_PROTOCOLS_STR="http,https" (CURLOPT_REDIR_PROTOCOLS bitmask fallback for libcurl < 7.85.0), UNRESTRICTED_AUTH=0L. Defense-in-depth on top of libcurl's automatic bearer strip on cross-host redirects (>=7.64.0). MEDIUM: - M1: upload_file resolves the path via std::filesystem::canonical up front. Closes broken-symlink, symlink-loop, and TOCTOU-on-target classes without a doc burden on callers. - M2: README "Linking" section documents the public-ABI nlohmann_json implication. v0.2 wrapper deferred. - M3: README "Threat model" section documents the parse-depth concern on the result field of /run replies. Runtime guard skipped for v0.1 per audit recommendation (low yield, complexity). LOW: - L1: cxx_std_20 → cxx_std_17 in CMakeLists.txt (no C++20-only features in the library source; broader downstream reach). Examples and tests still build via designated initializers (g++ accepts these in C++17 mode). - L2: RunResult struct doc clarifies that missing ok/duration_ms decode to defaults — opt-out forward-compat. - L3: Client class doc clarifies that moved-from instances must not have any non-special-member methods invoked (UB), with explicit callout on base_url() returning an internal reference. Test-only: - cpp-httplib 0.15.3 → 0.20.1. Optional backends (OpenSSL / zlib / brotli / zstd) forced off to keep the dep graph minimal. Test-only, never on the consumer wire path. README "Test deps" section added for transparency. Tests added (12 → 23 cases, 70 → 106 assertions): - protocol_error on malformed response for healthz, run, upload_file, create_token, list_tokens (H1 regression) - redirect_clamp_test (H2 regression — TransportError after 5+ hops) - redirect_protocol_clamp (H2 regression — ftp:// Location rejected) - upload_file_canonicalize: symlink→file works, broken symlink rejected, symlink loop rejected, directory rejected (M1 regression) Verified: - cmake --build build clean (-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wold-style-cast -Werror) - ctest --output-on-failure all green (Release) - ASan + UBSan: 23/23 cases, 106/106 assertions, zero diagnostics Audit: memory/clawdforge-audits/cpp-bae34a7.md
214 lines
7 KiB
CMake
214 lines
7 KiB
CMake
# SPDX-License-Identifier: MIT
|
|
cmake_minimum_required(VERSION 3.20)
|
|
|
|
project(clawdforge
|
|
VERSION 0.1.0
|
|
DESCRIPTION "C++ SDK for the clawdforge HTTP API"
|
|
LANGUAGES CXX
|
|
)
|
|
|
|
# ---- options ----------------------------------------------------------------
|
|
|
|
option(CLAWDFORGE_BUILD_TESTS "Build unit tests" ON)
|
|
option(CLAWDFORGE_BUILD_EXAMPLES "Build examples" ON)
|
|
option(CLAWDFORGE_WARNINGS_AS_ERRORS "Treat warnings as errors" ON)
|
|
|
|
# When consumed via add_subdirectory, default the extras off unless the user
|
|
# explicitly opts in. Top-level builds (the SDK's own CI / dev loop) keep them
|
|
# on so we exercise them.
|
|
if(NOT CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
|
set(CLAWDFORGE_BUILD_TESTS OFF)
|
|
set(CLAWDFORGE_BUILD_EXAMPLES OFF)
|
|
endif()
|
|
|
|
# ---- standard / flags -------------------------------------------------------
|
|
|
|
set(CMAKE_CXX_STANDARD 17)
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
|
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
|
|
endif()
|
|
|
|
include(CMakePackageConfigHelpers)
|
|
include(GNUInstallDirs)
|
|
include(FetchContent)
|
|
|
|
# ---- dependencies -----------------------------------------------------------
|
|
|
|
# nlohmann/json — try find_package first, fall back to FetchContent.
|
|
find_package(nlohmann_json 3.10 QUIET)
|
|
if(NOT nlohmann_json_FOUND)
|
|
message(STATUS "clawdforge: nlohmann_json not installed, fetching v3.11.3")
|
|
FetchContent_Declare(nlohmann_json
|
|
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
|
GIT_TAG v3.11.3
|
|
GIT_SHALLOW TRUE
|
|
)
|
|
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
|
set(JSON_Install ON CACHE INTERNAL "")
|
|
FetchContent_MakeAvailable(nlohmann_json)
|
|
endif()
|
|
|
|
# libcurl — system-installed.
|
|
find_package(CURL REQUIRED)
|
|
|
|
# ---- library target ---------------------------------------------------------
|
|
|
|
add_library(clawdforge)
|
|
add_library(clawdforge::clawdforge ALIAS clawdforge)
|
|
|
|
target_sources(clawdforge
|
|
PRIVATE
|
|
src/client.cpp
|
|
src/http.cpp
|
|
)
|
|
|
|
target_include_directories(clawdforge
|
|
PUBLIC
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
|
PRIVATE
|
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
|
)
|
|
|
|
target_link_libraries(clawdforge
|
|
PUBLIC
|
|
nlohmann_json::nlohmann_json
|
|
PRIVATE
|
|
CURL::libcurl
|
|
)
|
|
|
|
target_compile_features(clawdforge PUBLIC cxx_std_17)
|
|
|
|
set_target_properties(clawdforge PROPERTIES
|
|
CXX_VISIBILITY_PRESET hidden
|
|
VISIBILITY_INLINES_HIDDEN ON
|
|
POSITION_INDEPENDENT_CODE ON
|
|
VERSION ${PROJECT_VERSION}
|
|
SOVERSION ${PROJECT_VERSION_MAJOR}
|
|
EXPORT_NAME clawdforge
|
|
)
|
|
|
|
# Warnings -- private so consumers don't inherit our flags.
|
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|
target_compile_options(clawdforge PRIVATE
|
|
-Wall -Wextra -Wpedantic
|
|
-Wshadow -Wconversion -Wsign-conversion
|
|
-Wnon-virtual-dtor -Wold-style-cast
|
|
)
|
|
if(CLAWDFORGE_WARNINGS_AS_ERRORS)
|
|
target_compile_options(clawdforge PRIVATE -Werror)
|
|
endif()
|
|
elseif(MSVC)
|
|
target_compile_options(clawdforge PRIVATE /W4 /permissive-)
|
|
if(CLAWDFORGE_WARNINGS_AS_ERRORS)
|
|
target_compile_options(clawdforge PRIVATE /WX)
|
|
endif()
|
|
endif()
|
|
|
|
# ---- install ----------------------------------------------------------------
|
|
|
|
install(DIRECTORY include/clawdforge
|
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
|
FILES_MATCHING PATTERN "*.hpp"
|
|
)
|
|
|
|
install(TARGETS clawdforge
|
|
EXPORT clawdforge-targets
|
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
)
|
|
|
|
install(EXPORT clawdforge-targets
|
|
FILE clawdforge-targets.cmake
|
|
NAMESPACE clawdforge::
|
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/clawdforge
|
|
)
|
|
|
|
configure_package_config_file(
|
|
cmake/clawdforge-config.cmake.in
|
|
"${CMAKE_CURRENT_BINARY_DIR}/clawdforge-config.cmake"
|
|
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/clawdforge
|
|
)
|
|
|
|
write_basic_package_version_file(
|
|
"${CMAKE_CURRENT_BINARY_DIR}/clawdforge-config-version.cmake"
|
|
VERSION ${PROJECT_VERSION}
|
|
COMPATIBILITY SameMajorVersion
|
|
)
|
|
|
|
install(FILES
|
|
"${CMAKE_CURRENT_BINARY_DIR}/clawdforge-config.cmake"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/clawdforge-config-version.cmake"
|
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/clawdforge
|
|
)
|
|
|
|
# ---- examples ---------------------------------------------------------------
|
|
|
|
if(CLAWDFORGE_BUILD_EXAMPLES)
|
|
add_executable(clawdforge_basic_example examples/basic.cpp)
|
|
target_link_libraries(clawdforge_basic_example PRIVATE clawdforge::clawdforge)
|
|
endif()
|
|
|
|
# ---- tests ------------------------------------------------------------------
|
|
|
|
if(CLAWDFORGE_BUILD_TESTS)
|
|
enable_testing()
|
|
|
|
# cpp-httplib for an in-process mock server. Prefer system / installed copy.
|
|
# Test-only — never linked into the shipped library. Bumped from 0.15.3 to
|
|
# 0.20.1 for clean dep graphs (0.15.x has several published advisories;
|
|
# the SDK never exposes the mock to a network).
|
|
find_package(httplib QUIET CONFIG)
|
|
if(NOT httplib_FOUND)
|
|
message(STATUS "clawdforge: cpp-httplib not installed, fetching v0.20.1")
|
|
# The mock server doesn't need TLS / compression — disable optional
|
|
# backends so we don't drag in zstd / brotli / openssl find_package
|
|
# requirements for a test-only dep.
|
|
set(HTTPLIB_USE_OPENSSL_IF_AVAILABLE OFF CACHE INTERNAL "")
|
|
set(HTTPLIB_USE_ZLIB_IF_AVAILABLE OFF CACHE INTERNAL "")
|
|
set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF CACHE INTERNAL "")
|
|
set(HTTPLIB_USE_ZSTD_IF_AVAILABLE OFF CACHE INTERNAL "")
|
|
FetchContent_Declare(cpp_httplib
|
|
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
|
|
GIT_TAG v0.20.1
|
|
GIT_SHALLOW TRUE
|
|
)
|
|
FetchContent_MakeAvailable(cpp_httplib)
|
|
endif()
|
|
|
|
# doctest — single-header, fastest to compile of the popular options.
|
|
find_package(doctest QUIET CONFIG)
|
|
if(NOT doctest_FOUND)
|
|
message(STATUS "clawdforge: doctest not installed, fetching v2.4.11")
|
|
FetchContent_Declare(doctest
|
|
GIT_REPOSITORY https://github.com/doctest/doctest.git
|
|
GIT_TAG v2.4.11
|
|
GIT_SHALLOW TRUE
|
|
)
|
|
FetchContent_MakeAvailable(doctest)
|
|
endif()
|
|
|
|
add_executable(clawdforge_tests tests/test_client.cpp)
|
|
target_link_libraries(clawdforge_tests
|
|
PRIVATE
|
|
clawdforge::clawdforge
|
|
httplib::httplib
|
|
doctest::doctest
|
|
)
|
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|
target_compile_options(clawdforge_tests PRIVATE
|
|
-Wall -Wextra
|
|
# Designated initializers in C++20 don't need to set every member.
|
|
-Wno-missing-field-initializers
|
|
# CHECK_THROWS_AS expands to a discarded-value expression; the
|
|
# `[[nodiscard]]` on Client methods makes that legitimately noisy.
|
|
-Wno-unused-result
|
|
)
|
|
endif()
|
|
|
|
add_test(NAME clawdforge_tests COMMAND clawdforge_tests)
|
|
endif()
|