ci: Use Nix-based images for all workflows except pre-commit (#3098)

This commit is contained in:
Ayaz Salikhov
2026-06-16 22:50:43 +01:00
committed by GitHub
parent e24216c8a9
commit fa057bd876
69 changed files with 846 additions and 1431 deletions

View File

@@ -3,21 +3,9 @@ add_executable(clio_server)
target_sources(clio_server PRIVATE Main.cpp)
target_link_libraries(clio_server PRIVATE clio_app)
if(static)
if(san)
message(FATAL_ERROR "Static linkage not allowed when using sanitizers")
elseif(is_appleclang)
message(FATAL_ERROR "Static linkage not supported on AppleClang")
else()
target_link_options(
# Note: -static-libstdc++ can statically link both libstdc++ and libc++
clio_server
PRIVATE -static-libstdc++ -static-libgcc
)
endif()
endif()
set_target_properties(
clio_server
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
)
patch_nix_binary(clio_server)

View File

@@ -58,4 +58,4 @@ target_sources(
handlers/VaultInfo.cpp
)
target_link_libraries(clio_rpc PRIVATE clio_util)
target_link_libraries(clio_rpc PUBLIC clio_util clio_data)

View File

@@ -45,7 +45,7 @@ target_sources(
)
# This must be above the target_link_libraries call otherwise backtrace doesn't work
if("${san}" STREQUAL "")
if(NOT SANITIZERS_ENABLED)
target_link_libraries(clio_util PUBLIC Boost::stacktrace_backtrace)
endif()

View File

@@ -4,6 +4,7 @@
#include "util/async/AnyStopToken.hpp"
#include "util/async/AnyStrand.hpp"
#include "util/async/Concepts.hpp"
#include "util/async/impl/Any.hpp"
#include "util/async/impl/ErasedOperation.hpp"
#include <any>
@@ -68,10 +69,10 @@ public:
execute(SomeHandlerWithoutStopToken auto&& fn)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn)>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
return AnyOperation<RetType>(
pimpl_->execute([fn = std::forward<decltype(fn)>(fn)] mutable -> std::any {
pimpl_->execute([fn = std::forward<decltype(fn)>(fn)] mutable -> impl::Any {
if constexpr (std::is_void_v<RetType>) {
std::invoke(std::forward<decltype(fn)>(fn));
return {};
@@ -94,10 +95,10 @@ public:
execute(SomeHandlerWith<AnyStopToken> auto&& fn)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn), AnyStopToken>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
return AnyOperation<RetType>(pimpl_->execute(
[fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> std::any {
[fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> impl::Any {
if constexpr (std::is_void_v<RetType>) {
std::invoke(std::forward<decltype(fn)>(fn), std::move(stopToken));
return {};
@@ -123,10 +124,10 @@ public:
execute(SomeHandlerWith<AnyStopToken> auto&& fn, SomeStdDuration auto timeout)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn), AnyStopToken>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
return AnyOperation<RetType>(pimpl_->execute(
[fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> std::any {
[fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> impl::Any {
if constexpr (std::is_void_v<RetType>) {
std::invoke(std::forward<decltype(fn)>(fn), std::move(stopToken));
return {};
@@ -153,11 +154,11 @@ public:
scheduleAfter(SomeStdDuration auto delay, SomeHandlerWith<AnyStopToken> auto&& fn)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn), AnyStopToken>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(delay);
return AnyOperation<RetType>(pimpl_->scheduleAfter(
millis, [fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> std::any {
millis, [fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> impl::Any {
if constexpr (std::is_void_v<RetType>) {
std::invoke(std::forward<decltype(fn)>(fn), std::move(stopToken));
return {};
@@ -184,13 +185,13 @@ public:
scheduleAfter(SomeStdDuration auto delay, SomeHandlerWith<AnyStopToken, bool> auto&& fn)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn), AnyStopToken, bool>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(delay);
return AnyOperation<RetType>(pimpl_->scheduleAfter(
millis,
[fn = std::forward<decltype(fn)>(fn)](auto stopToken, auto cancelled) mutable
-> std::any {
-> impl::Any {
if constexpr (std::is_void_v<RetType>) {
std::invoke(std::forward<decltype(fn)>(fn), std::move(stopToken), cancelled);
return {};
@@ -214,12 +215,12 @@ public:
executeRepeatedly(SomeStdDuration auto interval, SomeHandlerWithoutStopToken auto&& fn)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn)>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(interval);
return AnyOperation<RetType>( //
pimpl_->executeRepeatedly(
millis, [fn = std::forward<decltype(fn)>(fn)] mutable -> std::any {
millis, [fn = std::forward<decltype(fn)>(fn)] mutable -> impl::Any {
std::invoke(std::forward<decltype(fn)>(fn));
return {};
}
@@ -277,18 +278,18 @@ private:
virtual impl::ErasedOperation
execute(
std::function<std::any(AnyStopToken)>,
std::function<impl::Any(AnyStopToken)>,
std::optional<std::chrono::milliseconds> timeout = std::nullopt
) = 0;
virtual impl::ErasedOperation execute(std::function<std::any()>) = 0;
virtual impl::ErasedOperation execute(std::function<impl::Any()>) = 0;
virtual impl::ErasedOperation
scheduleAfter(std::chrono::milliseconds, std::function<std::any(AnyStopToken)>) = 0;
scheduleAfter(std::chrono::milliseconds, std::function<impl::Any(AnyStopToken)>) = 0;
virtual impl::ErasedOperation scheduleAfter(
std::chrono::milliseconds,
std::function<std::any(AnyStopToken, bool)>
std::function<impl::Any(AnyStopToken, bool)>
) = 0;
virtual impl::ErasedOperation
executeRepeatedly(std::chrono::milliseconds, std::function<std::any()>) = 0;
executeRepeatedly(std::chrono::milliseconds, std::function<impl::Any()>) = 0;
virtual void submit(std::function<void()>) = 0;
virtual AnyStrand
makeStrand() = 0;
@@ -309,7 +310,7 @@ private:
impl::ErasedOperation
execute(
std::function<std::any(AnyStopToken)> fn,
std::function<impl::Any(AnyStopToken)> fn,
std::optional<std::chrono::milliseconds> timeout
) override
{
@@ -317,7 +318,7 @@ private:
}
impl::ErasedOperation
execute(std::function<std::any()> fn) override
execute(std::function<impl::Any()> fn) override
{
return ctx.execute(std::move(fn));
}
@@ -325,7 +326,7 @@ private:
impl::ErasedOperation
scheduleAfter(
std::chrono::milliseconds delay,
std::function<std::any(AnyStopToken)> fn
std::function<impl::Any(AnyStopToken)> fn
) override
{
return ctx.scheduleAfter(delay, std::move(fn));
@@ -334,14 +335,17 @@ private:
impl::ErasedOperation
scheduleAfter(
std::chrono::milliseconds delay,
std::function<std::any(AnyStopToken, bool)> fn
std::function<impl::Any(AnyStopToken, bool)> fn
) override
{
return ctx.scheduleAfter(delay, std::move(fn));
}
impl::ErasedOperation
executeRepeatedly(std::chrono::milliseconds interval, std::function<std::any()> fn) override
executeRepeatedly(
std::chrono::milliseconds interval,
std::function<impl::Any()> fn
) override
{
return ctx.executeRepeatedly(interval, std::move(fn));
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "util/async/Error.hpp"
#include "util/async/impl/Any.hpp"
#include "util/async/impl/ErasedOperation.hpp"
#include <fmt/format.h>

View File

@@ -3,6 +3,7 @@
#include "util/async/AnyOperation.hpp"
#include "util/async/AnyStopToken.hpp"
#include "util/async/Concepts.hpp"
#include "util/async/impl/Any.hpp"
#include "util/async/impl/ErasedOperation.hpp"
#include <any>
@@ -46,10 +47,10 @@ public:
execute(SomeHandlerWithoutStopToken auto&& fn)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn)>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
return AnyOperation<RetType>( //
pimpl_->execute([fn = std::forward<decltype(fn)>(fn)] mutable -> std::any {
pimpl_->execute([fn = std::forward<decltype(fn)>(fn)] mutable -> impl::Any {
if constexpr (std::is_void_v<RetType>) {
std::invoke(std::forward<decltype(fn)>(fn));
return {};
@@ -70,11 +71,11 @@ public:
execute(SomeHandlerWith<AnyStopToken> auto&& fn)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn), AnyStopToken>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
return AnyOperation<RetType>( //
pimpl_->execute(
[fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> std::any {
[fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> impl::Any {
if constexpr (std::is_void_v<RetType>) {
std::invoke(std::forward<decltype(fn)>(fn), std::move(stopToken));
return {};
@@ -99,11 +100,11 @@ public:
execute(SomeHandlerWith<AnyStopToken> auto&& fn, SomeStdDuration auto timeout)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn), AnyStopToken>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
return AnyOperation<RetType>( //
pimpl_->execute(
[fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> std::any {
[fn = std::forward<decltype(fn)>(fn)](auto stopToken) mutable -> impl::Any {
if constexpr (std::is_void_v<RetType>) {
std::invoke(std::forward<decltype(fn)>(fn), std::move(stopToken));
return {};
@@ -129,12 +130,12 @@ public:
executeRepeatedly(SomeStdDuration auto interval, SomeHandlerWithoutStopToken auto&& fn)
{
using RetType = std::decay_t<std::invoke_result_t<decltype(fn)>>;
static_assert(not std::is_same_v<RetType, std::any>);
static_assert(not std::is_same_v<RetType, impl::Any>);
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(interval);
return AnyOperation<RetType>( //
pimpl_->executeRepeatedly(
millis, [fn = std::forward<decltype(fn)>(fn)] mutable -> std::any {
millis, [fn = std::forward<decltype(fn)>(fn)] mutable -> impl::Any {
std::invoke(std::forward<decltype(fn)>(fn));
return {};
}
@@ -160,12 +161,12 @@ private:
[[nodiscard]] virtual impl::ErasedOperation
execute(
std::function<std::any(AnyStopToken)>,
std::function<impl::Any(AnyStopToken)>,
std::optional<std::chrono::milliseconds> timeout = std::nullopt
) = 0;
[[nodiscard]] virtual impl::ErasedOperation execute(std::function<std::any()>) = 0;
[[nodiscard]] virtual impl::ErasedOperation execute(std::function<impl::Any()>) = 0;
[[nodiscard]] virtual impl::ErasedOperation
executeRepeatedly(std::chrono::milliseconds, std::function<std::any()>) = 0;
executeRepeatedly(std::chrono::milliseconds, std::function<impl::Any()>) = 0;
virtual void submit(std::function<void()>) = 0;
};
@@ -181,7 +182,7 @@ private:
[[nodiscard]] impl::ErasedOperation
execute(
std::function<std::any(AnyStopToken)> fn,
std::function<impl::Any(AnyStopToken)> fn,
std::optional<std::chrono::milliseconds> timeout
) override
{
@@ -189,13 +190,16 @@ private:
}
[[nodiscard]] impl::ErasedOperation
execute(std::function<std::any()> fn) override
execute(std::function<impl::Any()> fn) override
{
return strand.execute(std::move(fn));
}
impl::ErasedOperation
executeRepeatedly(std::chrono::milliseconds interval, std::function<std::any()> fn) override
executeRepeatedly(
std::chrono::milliseconds interval,
std::function<impl::Any()> fn
) override
{
return strand.executeRepeatedly(interval, std::move(fn));
}

View File

@@ -6,11 +6,6 @@
#include <string>
#include <utility>
// for the static_assert at the bottom which fixes clang compilation:
// see: https://godbolt.org/z/fzTjMd7G1 vs https://godbolt.org/z/jhKG7deen
#include <any>
#include <expected>
namespace util::async {
/**
@@ -49,7 +44,4 @@ struct ExecutionError {
std::string message;
};
// these are not the droids you are looking for...
static_assert(std::is_copy_constructible_v<std::expected<std::any, ExecutionError>>);
} // namespace util::async

View File

@@ -0,0 +1,41 @@
#pragma once
#include <any>
#include <type_traits>
namespace util::async::impl {
/**
* @brief A wrapper for std::any used as the type-erased transport for async operation results.
*
* It exists to work around a recursive-constraint failure in libstdc++'s `<expected>` when the
* value type is a raw `std::any`: `std::any`'s greedy templated constructor makes
* `std::expected<std::any, E>` ask whether `std::any` is constructible from
* `std::expected<std::any, E>`, which re-enters the same constraint. Newer Clang rejects this as
* self-referential.
*
* `Any` only allows construction from `std::any` (never from an `expected`), which breaks the cycle
* while still allowing transparent unwrapping back to `std::any&`.
*/
class Any {
std::any value_;
public:
Any() = default;
Any(Any const&) = default;
Any(Any&&) = default;
// note: this needs to be `auto` instead of `std::any` because of a bug in gcc 11.4
Any(auto&& v)
requires(std::is_same_v<std::decay_t<decltype(v)>, std::any>)
: value_{std::forward<decltype(v)>(v)}
{
}
operator std::any&() noexcept
{
return value_;
}
};
} // namespace util::async::impl

View File

@@ -3,8 +3,8 @@
#include "util/Assert.hpp"
#include "util/async/Concepts.hpp"
#include "util/async/Error.hpp"
#include "util/async/impl/Any.hpp"
#include <any>
#include <expected>
#include <memory>
#include <type_traits>
@@ -37,7 +37,7 @@ public:
pimpl_->wait();
}
std::expected<std::any, ExecutionError>
std::expected<Any, ExecutionError>
get()
{
return pimpl_->get();
@@ -64,7 +64,7 @@ private:
virtual void
wait() noexcept = 0;
virtual std::expected<std::any, ExecutionError>
virtual std::expected<Any, ExecutionError>
get() = 0;
virtual void
abort() = 0;
@@ -93,14 +93,14 @@ private:
}
}
std::expected<std::any, ExecutionError>
std::expected<Any, ExecutionError>
get() override
{
if constexpr (not SomeOperationWithData<OpType>) {
ASSERT(false, "Called get() on an operation that does not support it");
std::unreachable();
} else {
// Note: return type of the operation was already wrapped to std::any by
// Note: return type of the operation was already wrapped to impl::Any by
// AnyExecutionContext
return operation.get();
}

View File

@@ -13,6 +13,7 @@
#include <array>
#include <cstddef>
#include <cstdlib>
#include <expected>
#include <filesystem>
#include <fstream>
@@ -43,18 +44,36 @@ constexpr std::array kCertFilePaths{
"/system/etc/security/cacerts", // Android
};
std::optional<std::string>
readCertificateFile(std::filesystem::path const& path)
{
if (not std::filesystem::exists(path)) {
return std::nullopt;
}
std::ifstream const fileStream{path, std::ios::in};
if (not fileStream.is_open()) {
return std::nullopt;
}
std::stringstream buffer;
buffer << fileStream.rdbuf();
return std::move(buffer).str();
}
std::expected<std::string, RequestError>
getRootCertificate()
{
// Honor the OpenSSL-standard SSL_CERT_FILE environment variable first. Some
// environments (e.g. the Nix-based CI/runtime image) point it at their CA
// bundle instead of installing certificates at the well-known system paths.
if (char const* const certFile = std::getenv("SSL_CERT_FILE"); certFile != nullptr) {
if (auto contents = readCertificateFile(certFile); contents.has_value()) {
return *std::move(contents);
}
}
for (auto const& path : kCertFilePaths) {
if (std::filesystem::exists(path)) {
std::ifstream const fileStream{path, std::ios::in};
if (not fileStream.is_open()) {
continue;
}
std::stringstream buffer;
buffer << fileStream.rdbuf();
return std::move(buffer).str();
if (auto contents = readCertificateFile(path); contents.has_value()) {
return *std::move(contents);
}
}
return std::unexpected{RequestError{"SSL setup failed: could not find root certificate"}};