Finding libcurl
libcurl is the multiprotocol file transfer library used by virtually every networked C/C++ application. CMake provides a first-class FindCURL module that discovers system-installed libcurl and exposes the modern CURL::libcurl imported target.
# CMakeLists.txt — Basic libcurl integration
cmake_minimum_required(VERSION 3.20)
project(HttpClient LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find system libcurl
find_package(CURL REQUIRED)
add_executable(http_client src/main.cpp src/downloader.cpp)
target_link_libraries(http_client PRIVATE CURL::libcurl)
# Print discovered details
message(STATUS "CURL found: ${CURL_FOUND}")
message(STATUS "CURL version: ${CURL_VERSION_STRING}")
message(STATUS "CURL include: ${CURL_INCLUDE_DIRS}")
message(STATUS "CURL libraries: ${CURL_LIBRARIES}")
CURL::libcurl imported target over the legacy ${CURL_LIBRARIES} variable. The target automatically propagates include directories, compile definitions, and handles transitive dependencies — especially important when curl links against OpenSSL or zlib.
For specifying a minimum version requirement:
# Require specific minimum version
find_package(CURL 7.80 REQUIRED)
# Or use version range (CMake 3.19+)
find_package(CURL 7.80...8.99 REQUIRED)
TLS Backend Selection
libcurl supports multiple TLS backends — OpenSSL, wolfSSL, mbedTLS, Schannel (Windows), and Secure Transport (macOS). The backend determines certificate handling, cipher support, and licensing implications.
# Detect which TLS backend curl was built with
find_package(CURL REQUIRED)
# Check via curl-config or pkg-config
execute_process(
COMMAND curl-config --ssl-backends
OUTPUT_VARIABLE CURL_SSL_BACKENDS
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
message(STATUS "CURL TLS backends: ${CURL_SSL_BACKENDS}")
# Verify OpenSSL is the TLS backend
if(CURL_SSL_BACKENDS MATCHES "OpenSSL")
message(STATUS "CURL uses OpenSSL — LGPL/Apache-2.0 compatible")
elseif(CURL_SSL_BACKENDS MATCHES "Schannel")
message(STATUS "CURL uses Windows Schannel — no extra dependencies")
elseif(CURL_SSL_BACKENDS MATCHES "SecureTransport")
message(STATUS "CURL uses macOS Secure Transport")
endif()
// src/tls_check.cpp — Runtime TLS backend verification
#include <curl/curl.h>
#include <iostream>
void printTlsInfo() {
curl_version_info_data* info = curl_version_info(CURLVERSION_NOW);
std::cout << "libcurl version: " << info->version << "\n";
std::cout << "SSL version: " << (info->ssl_version ? info->ssl_version : "none") << "\n";
std::cout << "Protocols: ";
for (const char* const* p = info->protocols; *p; ++p) {
std::cout << *p << " ";
}
std::cout << "\n";
}
Protocol Feature Detection
Not all curl builds support all protocols. CMake can detect available protocols at configure time and conditionally compile features.
# CMakeLists.txt — Protocol feature detection
find_package(CURL REQUIRED)
# Check for specific protocol support
include(CheckCSourceRuns)
set(CMAKE_REQUIRED_LIBRARIES CURL::libcurl)
# Test HTTPS support
check_c_source_runs("
#include <curl/curl.h>
int main() {
curl_version_info_data* info = curl_version_info(CURLVERSION_NOW);
return (info->features & CURL_VERSION_SSL) ? 0 : 1;
}
" CURL_HAS_SSL)
# Test HTTP/2 support
check_c_source_runs("
#include <curl/curl.h>
int main() {
curl_version_info_data* info = curl_version_info(CURLVERSION_NOW);
return (info->features & CURL_VERSION_HTTP2) ? 0 : 1;
}
" CURL_HAS_HTTP2)
if(CURL_HAS_SSL)
target_compile_definitions(http_client PRIVATE HAS_HTTPS=1)
endif()
if(CURL_HAS_HTTP2)
target_compile_definitions(http_client PRIVATE HAS_HTTP2=1)
endif()
libcurl-minimal) may lack HTTPS, HTTP/2, or even FTP support. Always detect features at configure time rather than assuming availability — especially in containerized CI environments.
Static vs Shared Linking
Linking libcurl statically bundles it into your binary, eliminating runtime dependencies but requiring you to also link curl's own dependencies (OpenSSL, zlib, etc.).
# CMakeLists.txt — Static curl linking
option(CURL_USE_STATIC "Link libcurl statically" OFF)
if(CURL_USE_STATIC)
# Tell FindCURL to prefer static libraries
set(CURL_USE_STATIC_LIBS ON)
# Static curl requires CURL_STATICLIB define
find_package(CURL REQUIRED)
target_compile_definitions(http_client PRIVATE CURL_STATICLIB)
# Static curl needs its dependencies explicitly
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
target_link_libraries(http_client PRIVATE
CURL::libcurl
OpenSSL::SSL
OpenSSL::Crypto
ZLIB::ZLIB
)
# Platform-specific socket libraries
if(WIN32)
target_link_libraries(http_client PRIVATE ws2_32 crypt32 wldap32)
endif()
else()
find_package(CURL REQUIRED)
target_link_libraries(http_client PRIVATE CURL::libcurl)
endif()
Building from Source
When system curl is too old or you need specific features, build curl from source using FetchContent or ExternalProject:
# CMakeLists.txt — Build curl from source via FetchContent
cmake_minimum_required(VERSION 3.20)
project(HttpClient LANGUAGES C CXX)
include(FetchContent)
# Disable curl features we don't need
set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(CURL_DISABLE_LDAP ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_LDAPS ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_TELNET ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_DICT ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_FILE ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_TFTP ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_RTSP ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_POP3 ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_IMAP ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_SMTP ON CACHE BOOL "" FORCE)
set(CURL_DISABLE_GOPHER ON CACHE BOOL "" FORCE)
set(HTTP_ONLY ON CACHE BOOL "" FORCE)
FetchContent_Declare(curl
GIT_REPOSITORY https://github.com/curl/curl.git
GIT_TAG curl-8_7_1
)
FetchContent_MakeAvailable(curl)
add_executable(http_client src/main.cpp)
target_link_libraries(http_client PRIVATE libcurl_static)
target_compile_definitions(http_client PRIVATE CURL_STATICLIB)
HTTP_ONLY flag is the most aggressive — it builds curl with only HTTP/HTTPS support.
CURL with OpenSSL
When building curl from source, you can explicitly specify OpenSSL as the TLS backend and control certificate bundle paths:
# Build curl with specific OpenSSL version
find_package(OpenSSL 3.0 REQUIRED)
set(CURL_USE_OPENSSL ON CACHE BOOL "" FORCE)
set(CURL_CA_BUNDLE "/etc/ssl/certs/ca-certificates.crt" CACHE STRING "" FORCE)
set(CURL_CA_PATH "/etc/ssl/certs" CACHE STRING "" FORCE)
FetchContent_Declare(curl
GIT_REPOSITORY https://github.com/curl/curl.git
GIT_TAG curl-8_7_1
)
FetchContent_MakeAvailable(curl)
// src/https_client.cpp — HTTPS request with certificate verification
#include <curl/curl.h>
#include <string>
#include <iostream>
static size_t writeCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
size_t totalSize = size * nmemb;
output->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
int main() {
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* curl = curl_easy_init();
if (curl) {
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/zen");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "CMake-Demo/1.0");
// TLS verification (always enable in production)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
CURLcode res = curl_easy_perform(curl);
if (res == CURLE_OK) {
std::cout << "Response: " << response << "\n";
} else {
std::cerr << "Error: " << curl_easy_strerror(res) << "\n";
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
Testing HTTP Endpoints in CTest
CTest can validate that HTTP services respond correctly. Combine curl's CLI with add_test for integration testing:
# CMakeLists.txt — HTTP integration tests
enable_testing()
# Find curl CLI for integration tests
find_program(CURL_EXECUTABLE curl REQUIRED)
# Test that our server starts and responds
add_test(
NAME integration_health_check
COMMAND ${CURL_EXECUTABLE}
--silent --fail
--max-time 5
--retry 3
--retry-delay 1
http://localhost:8080/health
)
set_tests_properties(integration_health_check PROPERTIES
LABELS "integration"
TIMEOUT 30
FIXTURES_REQUIRED server_running
)
# Test JSON API endpoint
add_test(
NAME integration_api_users
COMMAND ${CURL_EXECUTABLE}
--silent --fail
--header "Content-Type: application/json"
--header "Accept: application/json"
--max-time 10
http://localhost:8080/api/users
)
set_tests_properties(integration_api_users PROPERTIES
LABELS "integration"
TIMEOUT 30
)
# Test POST request
add_test(
NAME integration_api_create
COMMAND ${CURL_EXECUTABLE}
--silent --fail
--request POST
--header "Content-Type: application/json"
--data "{\"name\":\"test\",\"email\":\"test@example.com\"}"
--max-time 10
http://localhost:8080/api/users
)
# Run integration tests separately
# cmake --build . && ctest -L integration
FIXTURES_REQUIRED and FIXTURES_SETUP properties to ensure your server starts before HTTP tests run. Combine with --retry flags on the curl command to handle startup latency gracefully.