diff --git a/.agents/docs/2026-07-17-add-asio-plan.md b/.agents/docs/2026-07-17-add-asio-plan.md new file mode 100644 index 0000000..8087396 --- /dev/null +++ b/.agents/docs/2026-07-17-add-asio-plan.md @@ -0,0 +1,183 @@ +# Add standalone Asio 1.38.1 as a header-only package + +Date: 2026-07-17 + +This document defines the contribution scope for `compat.asio@1.38.1`. It is +based on the current `origin/main`, its active validation workflow, and mcpp +0.0.94. Dated repository guidance is treated as historical when it conflicts +with those live contracts. + +## 1. Scope + +This contribution adds only the upstream standalone, header-only Asio package. +Consumers use `#include ` and related upstream headers. + +The following work is intentionally excluded: + +- a native C++ module or `import asio;` interface; +- OpenSSL or wolfSSL integration; +- Boost.Context, Boost.Regex, or Boost.Date_Time integration; +- liburing integration; +- changes to repository contribution guidance or README content. + +Native module adaptation remains separate because it has a different build and +consumer contract and requires its own compatibility evidence. + +## 2. Upstream identity and archive evidence + +- Canonical repository: `https://github.com/chriskohlhoff/asio`. +- Release tag: `asio-1-38-1`, the latest numeric Asio tag observed on + 2026-07-17. +- Tag commit: `bbecff21a23b97c34641f0f1f08b28c91b9c77cf`. +- License: Boost Software License 1.0 (`BSL-1.0`), confirmed from upstream + `COPYING` and `LICENSE_1_0.txt` at the tag. +- Linux/macOS archive: tag tarball, SHA-256 + `2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc`, + CN-mirrored byte-identically at + `https://gitcode.com/mcpp-res/asio/releases/download/1.38.1/asio-1.38.1.tar.gz`. +- Windows archive: `asio-1.38.1-nosymlinks.tar.gz`, SHA-256 + `77f74094bb12cd867a6edbf5736bbed816c6ce0906e880de8573097a81714d89`, hosted at + `https://github.com/xlings-res/asio/releases/download/1.38.1/` (GLOBAL) and + `https://gitcode.com/mcpp-res/asio/releases/download/1.38.1/` (CN). + +Two independent downloads of the upstream tarball produced the same SHA-256. +All archives are wrapped in `asio-asio-1-38-1/`, and their public entry header +is `asio-asio-1-38-1/include/asio.hpp`. Therefore `*/include` is the required +consumer include root. + +Both upstream tag archive encodings (tar.gz and zip) contain +`asio/include -> ../include` and `asio/src -> ../src` POSIX symlink entries. +The Windows extraction path (`tar.exe` via xlings) cannot materialize them, so +both encodings fail on the Windows runner, and upstream publishes no +symlink-free asset for 1.38.x (GitHub has no release assets; SourceForge stops +at 1.36.0). The Windows entry therefore uses a repackaged variant of the +upstream tag tarball with only those two symlink entries removed +(`tar --delete` + `gzip -n -9`); all 1544 regular files are byte-identical to +upstream. Provenance is documented in the `xlings-res/asio` repository README. + +The upstream tag is annotated but not cryptographically signed. Reproducibility +is enforced by the descriptor's pinned archive digest. + +## 3. Package shape and descriptor contract + +Asio is a third-party project without an mcpp manifest in the selected release, +so the package uses an inline Form B descriptor at +`pkgs/c/compat.asio.lua`: + +- namespace: `compat`; +- full package name: `compat.asio`; +- published version: bare version `1.38.1`; +- platforms: Linux and macOS use the tag tarball; Windows uses the repackaged + symlink-free tarball to avoid the POSIX symlink extraction failure; +- include root: `*/include`; +- build target: a generated C anchor provides the buildable library target + required by the current package resolver; +- Linux link interface: `-pthread`; +- `import_std = false` because this package is consumed through textual headers. + +The intended post-publication CLI token is `compat:asio@1.38.1`, which maps to +the consumer declaration `[dependencies.compat] asio = "1.38.1"`. Before the +replacement PR is opened, that token must be exercised with the current mcpp +CLI in an isolated project rather than inferred only from the descriptor name. + +The default `standalone` feature contributes these public preprocessor defines: + +- `ASIO_STANDALONE`; +- `ASIO_HEADER_ONLY`; +- `ASIO_DISABLE_BOOST_CONTEXT_FIBER`; +- `ASIO_HAS_THREADS` — asio's own thread detection keys off CRT macros + (`_MT`/`_REENTRANT`/`_POSIX_THREADS`) that the workspace's llvm-on-Windows + toolchain does not define. Without the pin asio silently selects + `null_thread` and every internal-thread operation (notably the + waitable-timer wait thread behind `steady_timer`) throws + `operation_not_supported` (10045) at runtime — observed as the core, + coroutine, and network consumer tests failing on the Windows runner while + the thread-free tests passed. asio only ever tests + `defined(ASIO_HAS_THREADS)`, and the POSIX pthread selection still runs + beneath it, so the pin is a no-op on platforms where detection already + works (re-verified by the Linux consumer suite). + +mcpp 0.0.94 accepts feature `defines` in the current xpkg parser and propagates +them to consumers. The Asio surface test rejects builds where any of these +defines is absent, and generated compile commands are inspected during local +verification to confirm that the flags reach every consumer translation unit. + +The descriptor uses grammar already accepted by the live index contract. This +contribution does not change `index.toml` (`min_mcpp = "0.0.87"`) or the active +workflow pin (`MCPP_VERSION = "0.0.94"`). + +## 4. URL and mirror decision + +Linux/macOS keep the canonical upstream GitHub tarball as GLOBAL; a +byte-identical copy of that tarball is uploaded to +`gitcode.com/mcpp-res/asio` as the CN mirror (same SHA-256, verified after +upload). + +Windows cannot use either upstream tag archive encoding (both carry the POSIX +symlinks), so its GLOBAL asset is the repackaged symlink-free tarball hosted +in the ecosystem's resource org `github.com/xlings-res/asio`, with a +byte-identical CN copy on `gitcode.com/mcpp-res/asio`. Both hosts were +sha256-verified after upload against the descriptor's pinned digest. + +## 5. Consumer and test design + +The consumer project is `tests/examples/asio`, registered in the root workspace +and resolved through the local `compat` index. The current repository workflow +uses `mcpp test --workspace`, so the example follows the active `tests/*.cpp` +layout instead of the historical `src/main.cpp` runner layout. + +Six executable tests provide failure-capable assertions: + +- `core`: timers, executor work, dispatch, defer, and post behavior; +- `coroutine`: `co_spawn`, `use_awaitable`, and completion behavior; +- `experimental`: experimental channel send/receive behavior; +- `network`: loopback TCP accept/connect/read/write behavior; +- `platform`: platform-specific public types guarded by target macros; +- `surface`: representative public headers, public types, and required package + defines. + +The tests do not include or exercise the separate native module adapter. + +## 6. Validation contract + +The active workflow pins mcpp 0.0.94. Before opening the replacement PR, the +branch must provide fresh evidence for all locally available checks: + +1. run the descriptor syntax and mirror lint with the available local Lua 5.5; +2. parse `pkgs/c/compat.asio.lua` with mcpp 0.0.94; +3. run the targeted Asio consumer tests from isolated build state with + `MCPP_INDEX_MIRROR=GLOBAL`; +4. inspect the generated compile commands for all three public defines; +5. exercise `mcpp add compat:asio@1.38.1` in an isolated consumer project; +6. run `git diff --check` and confirm README is identical to `origin/main`. + +The replacement PR must then pass every check instantiated by the live +workflow, including the Linux, macOS, and Windows workspace matrix. Local macOS +success is not evidence for the other declared platforms. + +## 7. Documentation and change boundary + +README remains byte-identical to `origin/main`. This package contribution does +not update historical contribution instructions, even where they describe old +CI job names or old feature limitations. Any correction to those documents +requires a separate, evidence-backed audit and a separately scoped change. + +The replacement PR is limited to: + +- this design record; +- `pkgs/c/compat.asio.lua`; +- the root workspace registration; +- `tests/examples/asio` consumer configuration and tests. + +## 8. Acceptance criteria + +- upstream version, license, layout, and repeated archive digest are recorded; +- local descriptor lint passes with Lua 5.5, and the workflow's Lua 5.4 lint + passes in GitHub Actions; +- the CLI dependency token is verified through an isolated `mcpp add`; +- the six targeted consumer tests pass from isolated build state; +- the Windows workspace job downloads the ZIP and completes the Asio tests; +- README has no diff; +- the replacement PR contains no native module adaptation; +- all required GitHub Actions jobs pass before maintainer merge; +- publication remains the repository's automatic post-merge responsibility. diff --git a/mcpp.toml b/mcpp.toml index a7a8891..2d36607 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -9,6 +9,7 @@ [workspace] members = [ "tests/examples/archive", + "tests/examples/asio", "tests/examples/build-mcpp", "tests/examples/cjson", "tests/examples/core", diff --git a/pkgs/c/compat.asio.lua b/pkgs/c/compat.asio.lua new file mode 100644 index 0000000..1f0bc63 --- /dev/null +++ b/pkgs/c/compat.asio.lua @@ -0,0 +1,95 @@ +-- compat.asio — standalone Asio 1.38.1, exposed in upstream's default +-- header-only mode. The package intentionally does not provide optional +-- OpenSSL/wolfSSL, Boost.Context/Regex/Date_Time, or liburing dependencies. +package = { + spec = "1", + namespace = "compat", + name = "compat.asio", + description = "Standalone asynchronous I/O and networking library (header-only)", + licenses = {"BSL-1.0"}, + repo = "https://github.com/chriskohlhoff/asio", + type = "package", + + xpm = { + linux = { + ["1.38.1"] = { + url = { + GLOBAL = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/asio/releases/download/1.38.1/asio-1.38.1.tar.gz", + }, + sha256 = "2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc", + }, + }, + macosx = { + ["1.38.1"] = { + url = { + GLOBAL = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz", + CN = "https://gitcode.com/mcpp-res/asio/releases/download/1.38.1/asio-1.38.1.tar.gz", + }, + sha256 = "2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc", + }, + }, + windows = { + ["1.38.1"] = { + -- Upstream's tag archives (tar.gz AND zip) both carry two POSIX + -- symlinks (asio/include -> ../include, asio/src -> ../src) that + -- tar.exe cannot materialize on the Windows runner, and upstream + -- publishes no symlink-free asset for 1.38.x. This asset is the + -- upstream tag tarball with only those two symlink entries + -- removed (tar --delete); all 1544 regular files are + -- byte-identical to upstream. Provenance: xlings-res/asio README. + url = { + GLOBAL = "https://github.com/xlings-res/asio/releases/download/1.38.1/asio-1.38.1-nosymlinks.tar.gz", + CN = "https://gitcode.com/mcpp-res/asio/releases/download/1.38.1/asio-1.38.1-nosymlinks.tar.gz", + }, + sha256 = "77f74094bb12cd867a6edbf5736bbed816c6ce0906e880de8573097a81714d89", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- GitHub wraps the tag as asio-asio-1-38-1/; expose its public include root. + include_dirs = { "*/include" }, + -- Header-only packages still need a buildable target in mcpp. + generated_files = { + ["mcpp_generated/asio_anchor.c"] = [==[ +int mcpp_compat_asio_headers_anchor(void) { return 0; } +]==], + }, + sources = { "mcpp_generated/asio_anchor.c" }, + targets = { ["asio"] = { kind = "lib" } }, + -- Explicitly pin the package's public configuration. `standalone` is a + -- default feature so its defines propagate to every consumer TU. + -- + -- ASIO_HAS_THREADS: asio's own thread detection keys off CRT macros + -- (_MT/_REENTRANT/_POSIX_THREADS) that the workspace's llvm-on-Windows + -- toolchain does not define, so asio silently selects null_thread and + -- every internal-thread operation (e.g. the waitable-timer wait thread) + -- throws operation_not_supported (10045) at runtime. All supported + -- targets are multithreaded; pin the detection result. asio only ever + -- tests defined(ASIO_HAS_THREADS), and on POSIX the pthread selection + -- below it still runs, so this is a no-op where detection already works. + features = { + ["default"] = { implies = { "standalone" } }, + ["standalone"] = { + defines = { + "ASIO_STANDALONE", + "ASIO_HEADER_ONLY", + "ASIO_DISABLE_BOOST_CONTEXT_FIBER", + "ASIO_HAS_THREADS", + }, + }, + }, + deps = {}, + -- POSIX threading is detected by Asio from unistd.h feature macros; + -- retain the portable driver-level thread link contract on Linux. + linux = { + ldflags = { "-pthread" }, + }, + -- On the supported desktop MSVC-ABI route, Asio autolinks ws2_32.lib + -- and mswsock.lib. Do not inject GNU -l flags into native link.exe. + }, +} diff --git a/tests/examples/asio/mcpp.toml b/tests/examples/asio/mcpp.toml new file mode 100644 index 0000000..ba10afc --- /dev/null +++ b/tests/examples/asio/mcpp.toml @@ -0,0 +1,10 @@ +# Asio consumer test project: standalone, header-only, cross-platform behavior. +[package] +name = "asio-tests" +version = "0.1.0" + +[indices] +compat = { path = "../../.." } + +[dependencies.compat] +asio = "1.38.1" diff --git a/tests/examples/asio/tests/core.cpp b/tests/examples/asio/tests/core.cpp new file mode 100644 index 0000000..21f11fd --- /dev/null +++ b/tests/examples/asio/tests/core.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include + +#if !defined(_WIN32) +#ifndef ASIO_HAS_THREADS +#error "Asio must detect POSIX thread support" +#endif +#ifndef ASIO_HAS_PTHREADS +#error "Asio must select its pthread implementation on POSIX" +#endif +#endif + +int main() { + using namespace std::chrono_literals; + + asio::io_context io; + auto guard = asio::make_work_guard(io); + auto serial = asio::make_strand(io); + asio::steady_timer timer(io, 2ms); + + std::atomic posted{0}; + std::mutex order_mutex; + std::vector order; + bool timer_called = false; + + asio::post(serial, [&] { + std::lock_guard lock(order_mutex); + order.push_back(1); + ++posted; + }); + asio::post(serial, [&] { + std::lock_guard lock(order_mutex); + order.push_back(2); + ++posted; + }); + timer.async_wait([&](const asio::error_code& ec) { + timer_called = !ec; + guard.reset(); + }); + + asio::thread worker([&] { io.run(); }); + worker.join(); + + if (posted != 2 || !timer_called || order != std::vector{1, 2}) return 1; + + asio::thread_pool pool(2); + std::atomic pooled{0}; + asio::post(pool, [&] { ++pooled; }); + asio::post(pool, [&] { ++pooled; }); + pool.join(); + if (pooled != 2) return 2; + + asio::io_context cancel_io; + asio::steady_timer cancelled(cancel_io, 1h); + asio::cancellation_signal cancellation; + asio::error_code cancelled_ec; + bool cancelled_called = false; + cancelled.async_wait(asio::bind_cancellation_slot( + cancellation.slot(), + [&](const asio::error_code& ec) { + cancelled_ec = ec; + cancelled_called = true; + })); + cancellation.emit(asio::cancellation_type::all); + cancel_io.run(); + + return cancelled_called && cancelled_ec == asio::error::operation_aborted ? 0 : 3; +} diff --git a/tests/examples/asio/tests/coroutine.cpp b/tests/examples/asio/tests/coroutine.cpp new file mode 100644 index 0000000..eb39ad4 --- /dev/null +++ b/tests/examples/asio/tests/coroutine.cpp @@ -0,0 +1,34 @@ +#include + +#include + +#ifndef ASIO_HAS_CO_AWAIT +#error "C++23 compat.asio consumers must have co_await support" +#endif +#ifndef ASIO_HAS_STD_COROUTINE +#error "C++23 compat.asio consumers must use the standard coroutine library" +#endif + +asio::awaitable timer_value() { + asio::steady_timer timer(co_await asio::this_coro::executor); + timer.expires_after(std::chrono::milliseconds(1)); + co_await timer.async_wait(asio::use_awaitable); + co_return 42; +} + +int main() { + asio::io_context io; + int result = 0; + bool completed = false; + + asio::co_spawn(io, timer_value(), + [&](std::exception_ptr error, int value) { + if (!error) { + result = value; + completed = true; + } + }); + + io.run(); + return completed && result == 42 ? 0 : 1; +} diff --git a/tests/examples/asio/tests/experimental.cpp b/tests/examples/asio/tests/experimental.cpp new file mode 100644 index 0000000..816f857 --- /dev/null +++ b/tests/examples/asio/tests/experimental.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +int main() { + asio::io_context io; + + asio::experimental::channel channel(io, 1); + if (!channel.try_send(asio::error_code{}, "channel")) return 1; + std::string channel_value; + asio::error_code channel_error; + channel.async_receive([&](asio::error_code ec, std::string value) { + channel_error = ec; + channel_value = std::move(value); + }); + + asio::experimental::concurrent_channel concurrent(io, 1); + if (!concurrent.try_send(asio::error_code{}, 42)) return 2; + int concurrent_value = 0; + asio::error_code concurrent_error; + concurrent.async_receive([&](asio::error_code ec, int value) { + concurrent_error = ec; + concurrent_value = value; + }); + + auto promised = asio::post(io, asio::experimental::use_promise); + bool promise_completed = false; + promised([&] { promise_completed = true; }); + + io.run(); + + return !channel_error && channel_value == "channel" + && !concurrent_error && concurrent_value == 42 + && promise_completed ? 0 : 3; +} diff --git a/tests/examples/asio/tests/network.cpp b/tests/examples/asio/tests/network.cpp new file mode 100644 index 0000000..2494945 --- /dev/null +++ b/tests/examples/asio/tests/network.cpp @@ -0,0 +1,103 @@ +#include + +#include +#include +#include + +int main() { + using asio::ip::tcp; + using asio::ip::udp; + using namespace std::chrono_literals; + + asio::io_context io; + tcp::acceptor acceptor(io, {asio::ip::address_v4::loopback(), 0}); + tcp::socket server(io); + tcp::socket client(io); + asio::steady_timer deadline(io, 5s); + + const std::string ping = "ping"; + const std::string pong = "pong"; + std::array server_data{}; + std::array client_data{}; + bool accepted = false; + bool connected = false; + bool tcp_done = false; + bool timed_out = false; + int failure = 0; + + auto fail = [&](int code) { + if (failure == 0) failure = code; + asio::error_code ignored; + acceptor.close(ignored); + server.close(ignored); + client.close(ignored); + deadline.cancel(); + }; + + deadline.async_wait([&](const asio::error_code& ec) { + if (!ec) { + timed_out = true; + fail(90); + } + }); + + acceptor.async_accept(server, [&](const asio::error_code& ec) { + if (ec) return fail(1); + accepted = true; + asio::async_read(server, asio::buffer(server_data), + [&](const asio::error_code& read_ec, std::size_t n) { + if (read_ec || n != ping.size() + || std::string(server_data.data(), n) != ping) return fail(2); + asio::async_write(server, asio::buffer(pong), + [&](const asio::error_code& write_ec, std::size_t written) { + if (write_ec || written != pong.size()) fail(3); + }); + }); + }); + + client.async_connect( + {asio::ip::address_v4::loopback(), acceptor.local_endpoint().port()}, + [&](const asio::error_code& ec) { + if (ec) return fail(4); + connected = true; + asio::async_write(client, asio::buffer(ping), + [&](const asio::error_code& write_ec, std::size_t written) { + if (write_ec || written != ping.size()) return fail(5); + asio::async_read(client, asio::buffer(client_data), + [&](const asio::error_code& read_ec, std::size_t n) { + if (read_ec || n != pong.size() + || std::string(client_data.data(), n) != pong) return fail(6); + tcp_done = true; + deadline.cancel(); + }); + }); + }); + + io.run(); + if (failure || timed_out || !accepted || !connected || !tcp_done) return failure ? failure : 7; + + asio::io_context udp_io; + udp::socket receiver(udp_io, {asio::ip::address_v4::loopback(), 0}); + udp::socket sender(udp_io, {asio::ip::address_v4::loopback(), 0}); + const std::string datagram = "asio-udp"; + std::array received{}; + udp::endpoint remote; + bool receive_done = false; + bool send_done = false; + asio::error_code udp_failure; + + receiver.async_receive_from(asio::buffer(received), remote, + [&](const asio::error_code& ec, std::size_t n) { + udp_failure = ec; + receive_done = !ec && n == datagram.size() + && std::string(received.data(), n) == datagram; + }); + sender.async_send_to(asio::buffer(datagram), receiver.local_endpoint(), + [&](const asio::error_code& ec, std::size_t n) { + if (ec) udp_failure = ec; + send_done = !ec && n == datagram.size(); + }); + + udp_io.run(); + return !udp_failure && receive_done && send_done ? 0 : 8; +} diff --git a/tests/examples/asio/tests/platform.cpp b/tests/examples/asio/tests/platform.cpp new file mode 100644 index 0000000..eadbcd9 --- /dev/null +++ b/tests/examples/asio/tests/platform.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +int main() { +#if defined(ASIO_HAS_PIPE) + asio::io_context io; + asio::readable_pipe reader(io); + asio::writable_pipe writer(io); + asio::error_code ec; + asio::connect_pipe(reader, writer, ec); + if (ec) return 1; + + const std::string sent = "pipe"; + std::array received{}; + if (asio::write(writer, asio::buffer(sent), ec) != sent.size() || ec) return 2; + if (asio::read(reader, asio::buffer(received), ec) != sent.size() || ec) return 3; + if (std::string(received.data(), received.size()) != sent) return 4; +#endif + +#if defined(ASIO_HAS_LOCAL_SOCKETS) + static_assert(sizeof(asio::local::stream_protocol::endpoint) > 0); +#endif +#if defined(ASIO_HAS_POSIX_STREAM_DESCRIPTOR) + static_assert(sizeof(asio::posix::stream_descriptor::native_handle_type) > 0); +#endif +#if defined(ASIO_HAS_WINDOWS_STREAM_HANDLE) + static_assert(sizeof(asio::windows::stream_handle::native_handle_type) > 0); +#endif +#if defined(ASIO_HAS_WINDOWS_OBJECT_HANDLE) + static_assert(sizeof(asio::windows::object_handle::native_handle_type) > 0); +#endif +#if defined(ASIO_HAS_FILE) + static_assert(sizeof(asio::random_access_file::native_handle_type) > 0); +#endif +#if defined(ASIO_HAS_SERIAL_PORT) + static_assert(sizeof(asio::serial_port::native_handle_type) > 0); +#endif + + return 0; +} diff --git a/tests/examples/asio/tests/surface.cpp b/tests/examples/asio/tests/surface.cpp new file mode 100644 index 0000000..ca8e927 --- /dev/null +++ b/tests/examples/asio/tests/surface.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ASIO_STANDALONE +#error "compat.asio must expose standalone Asio to consumers" +#endif +#ifndef ASIO_HEADER_ONLY +#error "compat.asio must expose Asio's header-only configuration" +#endif +#ifndef ASIO_DISABLE_BOOST_CONTEXT_FIBER +#error "compat.asio must make the excluded Boost.Context spawn path explicit" +#endif +#ifdef ASIO_ENABLE_BOOST +#error "compat.asio must not enable Boost.Asio mode" +#endif +#ifdef ASIO_HAS_BOOST_CONFIG +#error "standalone Asio must not depend on Boost.Config" +#endif +#ifdef ASIO_HAS_BOOST_REGEX +#error "standalone Asio must not expose Boost.Regex overloads" +#endif +#ifdef ASIO_HAS_BOOST_DATE_TIME +#error "standalone Asio must not expose legacy Boost.Date_Time timers" +#endif +#ifdef ASIO_HAS_IO_URING +#error "compat.asio base package must not require liburing" +#endif + +#include + +int main() { + static_assert(std::is_class_v); + static_assert(std::is_class_v); + static_assert(std::is_class_v); + static_assert(std::is_class_v); + static_assert(std::is_class_v); + static_assert(std::is_class_v); + static_assert(std::is_class_v>); + static_assert(std::is_class_v>); + +#if defined(ASIO_HAS_POSIX_STREAM_DESCRIPTOR) + static_assert(std::is_class_v); +#endif +#if defined(ASIO_HAS_LOCAL_SOCKETS) + static_assert(std::is_class_v); +#endif +#if defined(ASIO_HAS_PIPE) + static_assert(std::is_class_v); + static_assert(std::is_class_v); +#endif +#if defined(ASIO_HAS_SERIAL_PORT) + static_assert(std::is_class_v); +#endif +#if defined(ASIO_HAS_WINDOWS_STREAM_HANDLE) + static_assert(std::is_class_v); +#endif +#if defined(ASIO_HAS_WINDOWS_OBJECT_HANDLE) + static_assert(std::is_class_v); +#endif +#if defined(ASIO_HAS_FILE) + static_assert(std::is_class_v); +#endif + + return 0; +}