diff --git a/.gitignore b/.gitignore index 26881008..94a2958b 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ dist ninja_build_release _builds _logs + +vcpkg_installed/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 56688c57..7000a62c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ project(leanp2p) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) +option(ENABLE_TEST_PLANS "Enable test-plans support" OFF) + find_package(Boost CONFIG REQUIRED filesystem random beast program_options) find_package(OpenSSL CONFIG REQUIRED) find_package(soralog CONFIG REQUIRED) @@ -16,6 +18,9 @@ find_package(libsecp256k1 CONFIG REQUIRED) find_package(Protobuf CONFIG REQUIRED) find_package(tsl_hat_trie CONFIG REQUIRED) find_package(Boost.DI CONFIG REQUIRED) +if(ENABLE_TEST_PLANS) + find_package(hiredis CONFIG REQUIRED) +endif() include(cmake/functions.cmake) include(cmake/install.cmake) @@ -28,3 +33,8 @@ add_subdirectory(src) # Examples add_subdirectory(example) + +# Test-plans +if(ENABLE_TEST_PLANS) + add_subdirectory(test-plans) +endif() diff --git a/CMakePresets.json b/CMakePresets.json index 69e9a20e..63037151 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,14 +1,25 @@ { - "version": 2, - "configurePresets": [ - { - "name": "default", - "generator": "Ninja", - "binaryDir": "${sourceDir}/build", - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", - "CMAKE_BUILD_TYPE": "Debug" - } - } - ] -} + "version": 2, + "configurePresets": [ + { + "name": "default", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "test-plans", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "CMAKE_BUILD_TYPE": "Debug", + "ENABLE_TEST_PLANS": "ON", + "VCPKG_MANIFEST_FEATURES": "test-plans" + } + } + ] +} \ No newline at end of file diff --git a/example/00-vcpkg-install/vcpkg-overlay/leanp2p/portfile.cmake b/example/00-vcpkg-install/vcpkg-overlay/leanp2p/portfile.cmake index 40d5a3a7..a864c2fc 100644 --- a/example/00-vcpkg-install/vcpkg-overlay/leanp2p/portfile.cmake +++ b/example/00-vcpkg-install/vcpkg-overlay/leanp2p/portfile.cmake @@ -2,8 +2,8 @@ vcpkg_check_linkage(ONLY_STATIC_LIBRARY) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO qdrvm/leanp2p - REF c4deae2f4269983253ce67a2f14506577a720972 - SHA512 be2542a3d51161abd7e8fc99b25ba9557c53fb7ee2dc6edccef16c87db76d23290b7f6df2cc197a1ae5365d49e7bef07e83948a9e62cb5a218dffc72cbfa9fc7 + REF 51e7a4f483433bed8ddca5bb8df969c27c590d4b + SHA512 4259788d600848c4d51b16e971fac8f8a13ad88f694cee551bf9461ba7869e2f0debc06548f883912d051161ed29c1b7c7dc15dbfc5a70b96b8d4c2927741dfe ) vcpkg_cmake_configure(SOURCE_PATH "${SOURCE_PATH}") vcpkg_cmake_install() diff --git a/src/network/listener_manager.cpp b/src/network/listener_manager.cpp index 9a6f290f..0ffd6fa3 100644 --- a/src/network/listener_manager.cpp +++ b/src/network/listener_manager.cpp @@ -109,6 +109,7 @@ namespace libp2p::network { for (auto it = begin; it != end;) { auto r = it->second->listen(it->first); if (!r) { + SL_WARN(log(), "Can't listen on {}", it->first, r.error()); // can not start listening on this multiaddr, remove listener it = listeners_.erase(it); } else { @@ -162,6 +163,13 @@ namespace libp2p::network { this->onConnection(std::move(item)); } }); + if (started) { + auto r = listener->listen(ma); + if (not r.has_value()) { + SL_WARN(log(), "Can't listen on {}: {}", ma, r.error()); + return r.error(); + } + } listeners_.insert({ma, std::move(listener)}); diff --git a/test-plans/CMakeLists.txt b/test-plans/CMakeLists.txt new file mode 100644 index 00000000..1e9c3782 --- /dev/null +++ b/test-plans/CMakeLists.txt @@ -0,0 +1,8 @@ +add_subdirectory(transport_interop) + +add_library(test_plans_common + common.cpp +) +target_link_libraries(test_plans_common + hiredis::hiredis +) diff --git a/test-plans/common.cpp b/test-plans/common.cpp new file mode 100644 index 00000000..eb891bf8 --- /dev/null +++ b/test-plans/common.cpp @@ -0,0 +1,190 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "common.hpp" + +#include +#include +#include + +timeval asTimeval(std::chrono::microseconds us) { + timeval tv; + auto s = std::chrono::duration_cast(us); + tv.tv_sec = s.count(); + tv.tv_usec = (us - s).count(); + return tv; +} + +std::optional getenv_opt(const char *name) { + if (const char *v = std::getenv(name)) { + return std::string{v}; + } + return std::nullopt; +} + +std::optional parseBool(std::string_view str) { + if (str == "true") { + return true; + } + if (str == "false") { + return false; + } + return std::nullopt; +} + +std::optional get_first_network_ip(libp2p::log::Logger log) { + std::optional result; + struct ifaddrs *ifaddr; + + if (getifaddrs(&ifaddr) == -1) { + log->error("getifaddrs failed"); + return std::nullopt; + } + + for (auto *ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr) { + continue; + } + + if ((ifa->ifa_flags & IFF_LOOPBACK) != 0 + or (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + if (ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr; + result = boost::asio::ip::make_address_v4(ntohl(addr->sin_addr.s_addr)) + .to_string(); + log->info( + "Found network interface {} with IP {}", ifa->ifa_name, *result); + break; + } + } + + freeifaddrs(ifaddr); + return result; +} + +TestTimeout::TestTimeout(Timeout timeout) + : start_{Clock::now()}, timeout_{timeout} {} + +TestTimeout::Remaining TestTimeout::remaining() const { + auto now = Clock::now(); + auto deadline = start_ + timeout_; + if (now > deadline) { + return Remaining::zero(); + } + return deadline - now; +} + +Redis::Redis(libp2p::log::Logger log, ContextPtr ctx) + : log_{std::move(log)}, ctx_{std::move(ctx)} {} + +outcome::result Redis::parseAddress(std::string_view str) { + auto colon_pos = str.find(":"); + if (colon_pos == str.npos) { + return Address{str, 6379}; + } + std::string host{str.substr(0, colon_pos)}; + BOOST_OUTCOME_TRY(auto port, parseInt(str.substr(colon_pos + 1))); + return Address{host, port}; +} + +outcome::result> Redis::connect( + std::string_view address_str, std::chrono::microseconds timeout) { + auto log = libp2p::log::createLogger("redis"); + BOOST_OUTCOME_TRY(auto address, parseAddress(address_str)); + ContextPtr ctx{redisConnectWithTimeout( + address.first.c_str(), address.second, asTimeval(timeout))}; + if (ctx == nullptr) { + SL_ERROR(log, "Failed to connect to redis"); + return Error::CONNECT_ERROR; + } + if (ctx->err != 0) { + SL_ERROR(log, "Failed to connect to redis: {}", ctx->errstr); + return Error::CONNECT_ERROR; + } + return std::make_shared(log, std::move(ctx)); +} + +outcome::result Redis::ping() { + BOOST_OUTCOME_TRY(auto reply, tryReply(redisCommand(ctx_.get(), "PING"))); + if (replyStr(*reply) != "PONG") { + return Error::PING_REPLY_ERROR; + } + return outcome::success(); +} + +outcome::result Redis::blpop(const std::string &key, + TimeoutDouble timeout) { + BOOST_OUTCOME_TRY( + auto reply, + tryReply(redisCommand( + ctx_.get(), + "BLPOP %s %d", + key.c_str(), + static_cast( + std::chrono::duration_cast(timeout) + .count())))); + auto value = replyArray(*reply, 1); + if (not value.has_value()) { + return Error::BLPOP_REPLY_ERROR; + } + return std::string{replyStr(**value)}; +} + +outcome::result Redis::rpush(const std::string &key, + const std::string &value) { + BOOST_OUTCOME_TRY(tryReply( + redisCommand(ctx_.get(), "RPUSH %s %s", key.c_str(), value.c_str()))); + return outcome::success(); +} + +std::string_view Redis::replyStr(const redisReply &reply) { + return {reply.str, reply.len}; +} + +std::optional Redis::replyArray(const redisReply &reply, + size_t index) { + if (index >= reply.elements) { + return std::nullopt; + } + return reply.element[index]; +} + +outcome::result Redis::tryReply(void *reply_ptr) { + if (reply_ptr == nullptr) { + return Error::NO_REPLY; + } + ReplyPtr reply{static_cast(reply_ptr)}; + if (reply->type == REDIS_REPLY_ERROR) { + SL_ERROR(log_, "reply error: {}", replyStr(*reply)); + return Error::REPLY_ERROR; + } + return std::move(reply); +} + +std::shared_ptr testRedisConnect(libp2p::log::Logger log, + std::string_view address, + const TestTimeout &timeout) { + auto redis_res = Redis::connect(address, timeout.remainingUs()); + if (not redis_res.has_value()) { + SL_FATAL(log, "redis connect error: {}", redis_res.error()); + } + auto &redis = redis_res.value(); + while (true) { + auto ping_res = redis->ping(); + if (ping_res.has_value()) { + break; + } + SL_WARN(log, "redis ping error: {}", ping_res.error()); + if (timeout.remaining().count() == 0) { + SL_FATAL(log, "redis ping timeout"); + } + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + } + return redis; +} diff --git a/test-plans/common.hpp b/test-plans/common.hpp new file mode 100644 index 00000000..6359b591 --- /dev/null +++ b/test-plans/common.hpp @@ -0,0 +1,147 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRY_OR_SL_FATAL(r, log, format, ...) \ + ({ \ + auto __r = (r); \ + if (not __r.has_value()) { \ + SL_FATAL(log, format, __VA_ARGS__ __VA_OPT__(, ) __r.error()) \ + } \ + __r.value(); \ + }) + +struct redisContext; + +timeval asTimeval(std::chrono::microseconds us); + +template +outcome::result parseInt(std::string_view str) { + T num; + auto r = std::from_chars(str.data(), str.data() + str.size(), num); + if (r.ec != std::errc{}) { + return make_error_code(r.ec); + } + if (r.ptr - str.data() != str.size()) { + return std::errc::invalid_argument; + } + return num; +} + +std::optional getenv_opt(const char *name); + +std::optional parseBool(std::string_view str); + +std::optional get_first_network_ip(libp2p::log::Logger log); + +struct TestTimeout { + using Clock = std::chrono::steady_clock; + using Timeout = std::chrono::seconds; + using Remaining = std::chrono::nanoseconds; + + TestTimeout(Timeout timeout); + + Remaining remaining() const; + + std::chrono::microseconds remainingUs() const { + return std::chrono::duration_cast(remaining()); + } + std::chrono::milliseconds remainingMs() const { + return std::chrono::duration_cast(remaining()); + } + + Clock::time_point start_; + Timeout timeout_; +}; + +struct redisContextDeleter { + static void operator()(redisContext *ptr) { + if (ptr != nullptr) { + redisFree(ptr); + } + } +}; + +struct redisReplyDeleter { + static void operator()(redisReply *ptr) { + if (ptr != nullptr) { + freeReplyObject(ptr); + } + } +}; + +class Redis { + public: + using ContextPtr = std::unique_ptr; + using ReplyPtr = std::unique_ptr; + + using TimeoutDouble = std::chrono::duration; + + enum class Error { + CONNECT_ERROR, + PARSE_PORT_ERROR, + NO_REPLY, + REPLY_ERROR, + PING_REPLY_ERROR, + BLPOP_REPLY_ERROR, + }; + Q_ENUM_ERROR_CODE_FRIEND(Error) { + using E = decltype(e); + switch (e) { + case E::CONNECT_ERROR: + return "CONNECT_ERROR"; + case E::PARSE_PORT_ERROR: + return "PARSE_PORT_ERROR"; + case E::NO_REPLY: + return "NO_REPLY"; + case E::REPLY_ERROR: + return "REPLY_ERROR"; + case E::PING_REPLY_ERROR: + return "PING_REPLY_ERROR"; + case E::BLPOP_REPLY_ERROR: + return "BLPOP_REPLY_ERROR"; + } + abort(); + } + + Redis(libp2p::log::Logger log, ContextPtr ctx); + + using Address = std::pair; + static outcome::result
parseAddress(std::string_view str); + + static outcome::result> connect( + std::string_view address_str, std::chrono::microseconds timeout); + + outcome::result ping(); + outcome::result blpop(const std::string &key, + TimeoutDouble timeout); + outcome::result rpush(const std::string &key, const std::string &value); + + static std::string_view replyStr(const redisReply &reply); + static std::optional replyArray(const redisReply &reply, + size_t index); + + private: + outcome::result tryReply(void *reply_ptr); + + libp2p::log::Logger log_; + ContextPtr ctx_; +}; + +std::shared_ptr testRedisConnect(libp2p::log::Logger log, + std::string_view address, + const TestTimeout &timeout); diff --git a/test-plans/transport_interop/CMakeLists.txt b/test-plans/transport_interop/CMakeLists.txt new file mode 100644 index 00000000..15e3a1c6 --- /dev/null +++ b/test-plans/transport_interop/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(test_plans_ping + test_plans_ping.cpp +) +target_link_libraries(test_plans_ping + libp2p + p2p_protocol_ping + test_plans_common +) diff --git a/test-plans/transport_interop/Dockerfile b/test-plans/transport_interop/Dockerfile new file mode 100644 index 00000000..3612555a --- /dev/null +++ b/test-plans/transport_interop/Dockerfile @@ -0,0 +1,40 @@ +FROM ubuntu:24.04 AS builder + +RUN apt-get update && apt-get install -y \ + software-properties-common \ + && add-apt-repository ppa:ubuntu-toolchain-r/test \ + && apt-get update && apt-get install -y \ + git \ + cmake \ + ninja-build \ + curl \ + zip \ + unzip \ + tar \ + pkg-config \ + gcc-14 \ + g++-14 \ + nasm \ + && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 \ + && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 \ + && update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-14 100 \ + && rm -rf /var/lib/apt/lists/* + +ENV VCPKG_ROOT=/opt/vcpkg +RUN git clone https://github.com/microsoft/vcpkg.git $VCPKG_ROOT \ + && $VCPKG_ROOT/bootstrap-vcpkg.sh \ + && rm -rf $VCPKG_ROOT/.git + +WORKDIR /build +COPY . . + +RUN cmake --preset test-plans -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_EXE_LINKER_FLAGS="-static" \ + && cmake --build build -j$(nproc) --target test_plans_ping + +FROM alpine:3.19 + +COPY --from=builder /build/build/test-plans/transport_interop/test_plans_ping /test_plans_ping + +ENTRYPOINT ["/test_plans_ping"] \ No newline at end of file diff --git a/test-plans/transport_interop/test_plans_ping.cpp b/test-plans/transport_interop/test_plans_ping.cpp new file mode 100644 index 00000000..4864d2d6 --- /dev/null +++ b/test-plans/transport_interop/test_plans_ping.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common.hpp" + +int main() { + setlinebuf(stdout); + setlinebuf(stderr); + + libp2p::simpleLoggingSystem(); + auto log = libp2p::log::createLogger("Ping"); + + auto transport = getenv_opt("TRANSPORT"); + auto muxer = getenv_opt( + "MUXER"); // There for future use as skipped when transport=quic-v1 + auto secureChannel = + getenv_opt("SECURE_CHANNEL"); // There for future use as skipped when + // transport=quic-v1 + auto isDialer = getenv_opt("IS_DIALER").and_then(parseBool).value_or(false); + std::string listener_ip = + getenv_opt("LISTENER_IP") + .value_or("0.0.0.0"); // When we create sample peer, it binds on + // 0.0.0.0 by default + std::string redisAddr = getenv_opt("REDIS_ADDR").value_or("redis"); + auto testKey = getenv_opt("TEST_KEY"); + bool debug = getenv_opt("DEBUG").and_then(parseBool).value_or(false); + + TestTimeout timeout{std::chrono::seconds{300}}; + + if (!debug) { + log->setLevel(libp2p::log::Level::ERROR); + } + + // Detect actual network IP if listener_ip is 0.0.0.0 + // This has to be done manually right now + std::string ip = listener_ip; + if (not isDialer and listener_ip == "0.0.0.0") { + auto detected_ip = get_first_network_ip(log); + if (detected_ip.has_value()) { + ip = detected_ip.value(); + SL_INFO(log, "Using detected network IP: {}", ip); + } else { + SL_WARN(log, "Could not detect network IP, using 0.0.0.0"); + } + } + + std::string redisKey = fmt::format("{}_listener_multiaddr", *testKey); + SL_INFO(log, "Redis key: {}", redisKey); + + auto redis = testRedisConnect(log, redisAddr, timeout); + + unsigned int random_seed = static_cast(std::random_device{}()); + auto sample_peer = + libp2p::SamplePeer(random_seed, + ip, + libp2p::SamplePeer::samplePort(random_seed), + libp2p::SamplePeer::Ed25519); + + std::shared_ptr io_context; + std::shared_ptr host; + std::shared_ptr ping; + std::shared_ptr injector_lifetime; + if (*transport == "quic-v1") { + auto make_injector = [&] { + return libp2p::injector::makeHostInjector( + libp2p::injector::useKeyPair(sample_peer.keypair), + libp2p::injector::useTransportAdaptors< + libp2p::transport::QuicTransport>()); + }; + auto injector = + std::make_shared(make_injector()); + injector_lifetime = injector; + + io_context = injector->create>(); + host = injector->create>(); + ping = injector->create>(); + + } else { + SL_FATAL(log, "Unsupported transport protocol"); + } + + host->start(); + host->listenProtocol(ping); + ping->start(); + + if (isDialer) { + auto address_str = + TRY_OR_SL_FATAL(redis->blpop(redisKey, timeout.remaining()), + log, + "Retrieve listener address error: {}"); + SL_INFO(log, "Retrieved listener address from redis: {}", address_str); + auto address = TRY_OR_SL_FATAL(libp2p::Multiaddress::create(address_str), + log, + "Parse listener address error: {}"); + auto peer_id_str = address.getPeerId(); + if (not peer_id_str.has_value()) { + SL_FATAL(log, "No peer id in listener address"); + } + auto peer_id = TRY_OR_SL_FATAL(libp2p::PeerId::fromBase58(*peer_id_str), + log, + "Parse listener address peer id"); + libp2p::peer::PeerInfo connect_info{peer_id, {address}}; + + int exitStatus = 0; + libp2p::coroSpawn(*io_context, [&]() -> libp2p::Coro { + SL_INFO(log, "Connecting to {}", connect_info.addresses.at(0)); + auto handShakeStart = std::chrono::steady_clock::now(); + auto connection = TRY_OR_SL_FATAL( + co_await host->connect(connect_info), log, "Connect error: {}"); + SL_INFO(log, "Connected successfully"); + auto ping_rtt = TRY_OR_SL_FATAL( + co_await ping->ping(connection, timeout.remainingMs()), + log, + "Ping error: {}"); + auto handShakeEnd = std::chrono::steady_clock::now(); + SL_INFO(log, "Ping successful"); + + auto handShakePlusOneRTT_ms = + std::chrono::duration_cast( + handShakeEnd - handShakeStart); + auto ping_rtt_ms = + std::chrono::duration_cast(ping_rtt); + + SL_INFO(log, "latency:"); + SL_INFO( + log, " handshake_plus_one_rtt: {}", handShakePlusOneRTT_ms.count()); + SL_INFO(log, " ping_rtt: {}", ping_rtt_ms.count()); + SL_INFO(log, " unit: ms"); + + io_context->stop(); + }); + io_context->run_for(timeout.remaining()); + } else { + TRY_OR_SL_FATAL(host->listen(sample_peer.listen), + log, + "Error listening on {}: {}", + sample_peer.listen); + std::string address{sample_peer.connect.getStringAddress()}; + SL_INFO(log, "Pushing connect address {}", address); + TRY_OR_SL_FATAL( + redis->rpush(redisKey, address), log, "Push connect address error: {}"); + SL_INFO(log, "Listener address pushed to redis"); + io_context->run_for(timeout.remaining()); + SL_INFO(log, "Listener exiting"); + } + return EXIT_SUCCESS; +} diff --git a/vcpkg-overlay/qtils/portfile.cmake b/vcpkg-overlay/qtils/portfile.cmake index 964ee973..75da5e32 100644 --- a/vcpkg-overlay/qtils/portfile.cmake +++ b/vcpkg-overlay/qtils/portfile.cmake @@ -2,8 +2,8 @@ vcpkg_check_linkage(ONLY_STATIC_LIBRARY) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO qdrvm/qtils - REF 4749d1f8332e848dbce2ac74fc1a0c1992e9cbdb - SHA512 5a3366d5a162b70461e73740313b5c1f43f4958bc52aab64372b9992d67f338b0c869125b00fe3cad599e368bd6918309a2aa4b1981f595eccb014f1bc655887 + REF refs/tags/v0.1.6 + SHA512 7f4bcc40f1201b40510b9f9f5aee6c65b4f611b5b001e7e0996f3b9cdd852c388ceed29cb3a3d3302f90edee7dd62422e33c712752c58dc9b2a95141169d8fec ) vcpkg_cmake_configure(SOURCE_PATH "${SOURCE_PATH}") vcpkg_cmake_install() diff --git a/vcpkg-overlay/soralog/portfile.cmake b/vcpkg-overlay/soralog/portfile.cmake index 0b96e8e4..5767b112 100644 --- a/vcpkg-overlay/soralog/portfile.cmake +++ b/vcpkg-overlay/soralog/portfile.cmake @@ -1,9 +1,9 @@ vcpkg_check_linkage(ONLY_STATIC_LIBRARY) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH - REPO qdrvm/soralog - REF refs/tags/v0.2.5 - SHA512 47375cc61c78ebc4119781bf19ce3b92c4a5a40ed4dc77c0156ac0750df1e4d13455bf6f60d9ea2f0b7bf7dda75423eed320edce453617ab06d6c1c9a8a8843c + REPO xDimon/soralog + REF refs/tags/v0.2.6 + SHA512 8ad2698cf029b70e909d7ace1e500957dceecc80e2331cecd1d66e775bb19456c81957e0c375ce81010f0aa5c8b6f13dc38fbf25c8f8547833548e350f6ae3f3 ) vcpkg_cmake_configure(SOURCE_PATH "${SOURCE_PATH}") vcpkg_cmake_install() diff --git a/vcpkg.json b/vcpkg.json index 1316b656..cbd31a9f 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,12 @@ { "name": "leanp2p", "version": "0.1.0", + "features": { + "test-plans": { + "description": "Enable Redis support (needed for test-plans)", + "dependencies": ["hiredis"] + } + }, "dependencies": [ "boost-asio", "boost-beast",