Skip to content

Commit bf79ef5

Browse files
wellweiwellwei
andauthored
feat(chriskohlhoff.asio): import asio; — 将 Asio 1.38.1 适配为 C++23 模块 (separate compilation) (#80)
* feat(compat.asio-m): adapt Asio 1.38.1 as C++23 module (separate compilation) Add compat.asio-m@1.38.1 — a FORM B inline descriptor exposing standalone asio as 'import asio;' with ASIO_SEPARATE_COMPILATION mode. Changes: - pkgs/c/compat.asio-m.lua: Form B descriptor with generated module wrapper (37 asio headers included, ~55 using declarations exported: core/io, TCP/UDP networking, cancellation, experimental channel/use_promise, executor completeness, and common token adapters) - tests/examples/asio-module/: 5 tests (core, coroutine, experimental, network, surface) covering the full exported API surface - mcpp.toml: add tests/examples/asio-module to workspace members Tested: mcpp test -p asio-module => 5/5 passed Regression: mcpp test -p asio => 6/6 passed mcpp test -p spdlog, nlohmann.json, marzer.tomlplusplus => all OK Closes #73 (companion to compat.asio PR) * fix(compat.asio-m): export make_error_code(channel_errors) for GCC modules ADL On GCC (Linux CI), the make_error_code(channel_errors) function declared in asio/experimental/channel_error.hpp is in the global module fragment and NOT reachable from the importing TU via ADL. This causes: error: use of deleted function 'void std::__adl_only::make_error_code()' when the channel template destructor instantiates channel_traits::invoke_receive_cancelled() which constructs std::error_code from error::channel_cancelled. Fix: add an explicit exported using declaration: export namespace asio::experimental::error { using ::asio::experimental::error::make_error_code; } Clang (macOS) apparently tolerates the missing export, but GCC requires it. Also include channel_error.hpp explicitly in the global fragment. * refactor(compat.asio-m -> chriskohlhoff.asio): rename per upstream review Upstream requested package name change from compat.asio-m to chriskohlhoff.asio (namespace chriskohlhoff). Consumers can now use: mcpp add chriskohlhoff.asio@1.38.1 (full qualified) mcpp add asio@1.38.1 (shorthand) This is purely a package-name and namespace rename; the C++ module surface (import asio;) is unchanged. Also update the test project's mcpp.toml to match the new index. * docs(chriskohlhoff.asio): warn against ambiguous shorthand mcpp add asio@1.38.1 The shorthand 'asio' matches multiple packages in the default registry (compat.asio, mcpplibs.asio), causing resolution conflict. Consumers must use the fully qualified name 'chriskohlhoff.asio' instead. mcpp add chriskohlhoff.asio@1.38.1 (correct, unambiguous) mcpp add asio@1.38.1 (ambiguous, may fail or resolve wrongly) --------- Co-authored-by: wellwei <1827104243@qq.com>
1 parent 0d5715e commit bf79ef5

8 files changed

Lines changed: 728 additions & 0 deletions

File tree

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
members = [
1111
"tests/examples/archive",
1212
"tests/examples/asio",
13+
"tests/examples/asio-module",
1314
"tests/examples/build-mcpp",
1415
"tests/examples/cjson",
1516
"tests/examples/core",

pkgs/c/chriskohlhoff.asio.lua

Lines changed: 419 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Asio C++23-module consumer test project: `import asio;` (chriskohlhoff.asio,
2+
# Form B inline descriptor, separate-compilation mode). Complements
3+
# tests/examples/asio, which exercises the same upstream in header-only
4+
# `#include <asio.hpp>` form.
5+
[package]
6+
name = "asio-module-tests"
7+
version = "0.1.0"
8+
9+
[indices]
10+
chriskohlhoff = { path = "../../.." }
11+
12+
[dependencies.chriskohlhoff]
13+
asio = "1.38.1"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Core executor/timer behavior over the module surface: strand FIFO order,
2+
// work-guard-driven run loop, thread_pool, and timer cancellation mapping to
3+
// asio::error::operation_aborted. Module consumers pair `import asio;` with
4+
// `import std;` (no text #include mixing).
5+
import std;
6+
import asio;
7+
8+
int main() {
9+
using namespace std::chrono_literals;
10+
11+
asio::io_context io;
12+
auto guard = asio::make_work_guard(io);
13+
auto serial = asio::make_strand(io);
14+
asio::steady_timer timer(io, 2ms);
15+
16+
std::atomic<int> posted{0};
17+
std::mutex order_mutex;
18+
std::vector<int> order;
19+
bool timer_called = false;
20+
21+
asio::post(serial, [&] {
22+
std::lock_guard lock(order_mutex);
23+
order.push_back(1);
24+
++posted;
25+
});
26+
asio::post(serial, [&] {
27+
std::lock_guard lock(order_mutex);
28+
order.push_back(2);
29+
++posted;
30+
});
31+
timer.async_wait([&](const std::error_code& ec) {
32+
timer_called = !ec;
33+
guard.reset();
34+
});
35+
36+
std::thread worker([&] { io.run(); });
37+
worker.join();
38+
if (posted != 2 || !timer_called || order != std::vector<int>{1, 2}) return 1;
39+
40+
asio::thread_pool pool(2);
41+
std::atomic<int> pooled{0};
42+
asio::post(pool, [&] { ++pooled; });
43+
asio::post(pool, [&] { ++pooled; });
44+
pool.join();
45+
if (pooled != 2) return 2;
46+
47+
asio::io_context cancel_io;
48+
asio::steady_timer cancelled(cancel_io, 1h);
49+
asio::cancellation_signal cancellation;
50+
std::error_code cancelled_ec;
51+
bool cancelled_called = false;
52+
cancelled.async_wait(asio::bind_cancellation_slot(
53+
cancellation.slot(),
54+
[&](const std::error_code& ec) {
55+
cancelled_ec = ec;
56+
cancelled_called = true;
57+
}));
58+
cancellation.emit(asio::cancellation_type::all);
59+
cancel_io.run();
60+
61+
return cancelled_called && cancelled_ec == asio::error::operation_aborted ? 0 : 3;
62+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Coroutine surface: awaitable/co_spawn/use_awaitable/this_coro over the
2+
// module boundary, including a real timer suspension point.
3+
import std;
4+
import asio;
5+
6+
asio::awaitable<int> answer() {
7+
auto ex = co_await asio::this_coro::executor;
8+
asio::steady_timer t(ex, std::chrono::milliseconds(1));
9+
co_await t.async_wait(asio::use_awaitable);
10+
co_return 42;
11+
}
12+
13+
int main() {
14+
asio::io_context io;
15+
int result = 0;
16+
asio::co_spawn(io, answer(), [&](std::exception_ptr, int v) { result = v; });
17+
io.run();
18+
return result == 42 ? 0 : 1;
19+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Experimental channel/concurrent_channel/use_promise over the module surface.
2+
// Mirrors tests/examples/asio/tests/experimental.cpp.
3+
import std;
4+
import asio;
5+
6+
int main() {
7+
asio::io_context io;
8+
9+
asio::experimental::channel<void(std::error_code, std::string)> ch(io, 1);
10+
if (!ch.try_send(std::error_code{}, "channel")) return 1;
11+
std::string channel_value;
12+
std::error_code channel_error;
13+
ch.async_receive([&](std::error_code ec, std::string value) {
14+
channel_error = ec;
15+
channel_value = std::move(value);
16+
});
17+
18+
asio::experimental::concurrent_channel<void(std::error_code, int)> concurrent(io, 1);
19+
if (!concurrent.try_send(std::error_code{}, 42)) return 2;
20+
int concurrent_value = 0;
21+
std::error_code concurrent_error;
22+
concurrent.async_receive([&](std::error_code ec, int value) {
23+
concurrent_error = ec;
24+
concurrent_value = value;
25+
});
26+
27+
auto promised = asio::post(io, asio::experimental::use_promise);
28+
bool promise_completed = false;
29+
promised([&] { promise_completed = true; });
30+
31+
io.run();
32+
33+
return !channel_error && channel_value == "channel"
34+
&& !concurrent_error && concurrent_value == 42
35+
&& promise_completed ? 0 : 3;
36+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// TCP (acceptor/socket, async_read/async_write) and UDP (datagram send/receive)
2+
// over the module surface. Mirrors tests/examples/asio/tests/network.cpp.
3+
import std;
4+
import asio;
5+
6+
int main() {
7+
using namespace std::chrono_literals;
8+
9+
// --- TCP echo ---
10+
asio::io_context io;
11+
asio::ip::tcp::acceptor acceptor(io, {asio::ip::address_v4::loopback(), 0});
12+
asio::ip::tcp::socket server(io);
13+
asio::ip::tcp::socket client(io);
14+
asio::steady_timer deadline(io, 5s);
15+
16+
const std::string ping = "ping";
17+
const std::string pong = "pong";
18+
std::array<char, 4> server_data{};
19+
std::array<char, 4> client_data{};
20+
bool accepted = false;
21+
bool connected = false;
22+
bool tcp_done = false;
23+
bool timed_out = false;
24+
int failure = 0;
25+
26+
auto fail = [&](int code) {
27+
if (failure == 0) failure = code;
28+
std::error_code ignored;
29+
acceptor.close(ignored);
30+
server.close(ignored);
31+
client.close(ignored);
32+
deadline.cancel();
33+
};
34+
35+
deadline.async_wait([&](const std::error_code& ec) {
36+
if (!ec) {
37+
timed_out = true;
38+
fail(90);
39+
}
40+
});
41+
42+
acceptor.async_accept(server, [&](const std::error_code& ec) {
43+
if (ec) return fail(1);
44+
accepted = true;
45+
asio::async_read(server, asio::buffer(server_data),
46+
[&](const std::error_code& read_ec, std::size_t n) {
47+
if (read_ec || n != ping.size()
48+
|| std::string(server_data.data(), n) != ping) return fail(2);
49+
asio::async_write(server, asio::buffer(pong),
50+
[&](const std::error_code& write_ec, std::size_t written) {
51+
if (write_ec || written != pong.size()) fail(3);
52+
});
53+
});
54+
});
55+
56+
client.async_connect(
57+
{asio::ip::address_v4::loopback(), acceptor.local_endpoint().port()},
58+
[&](const std::error_code& ec) {
59+
if (ec) return fail(4);
60+
connected = true;
61+
asio::async_write(client, asio::buffer(ping),
62+
[&](const std::error_code& write_ec, std::size_t written) {
63+
if (write_ec || written != ping.size()) return fail(5);
64+
asio::async_read(client, asio::buffer(client_data),
65+
[&](const std::error_code& read_ec, std::size_t n) {
66+
if (read_ec || n != pong.size()
67+
|| std::string(client_data.data(), n) != pong) return fail(6);
68+
tcp_done = true;
69+
deadline.cancel();
70+
});
71+
});
72+
});
73+
74+
io.run();
75+
if (failure || timed_out || !accepted || !connected || !tcp_done) return failure ? failure : 7;
76+
77+
// --- UDP datagram ---
78+
asio::io_context udp_io;
79+
asio::ip::udp::socket receiver(udp_io, {asio::ip::address_v4::loopback(), 0});
80+
asio::ip::udp::socket sender(udp_io, {asio::ip::address_v4::loopback(), 0});
81+
const std::string datagram = "asio-udp";
82+
std::array<char, 8> received{};
83+
asio::ip::udp::endpoint remote;
84+
bool receive_done = false;
85+
bool send_done = false;
86+
std::error_code udp_failure;
87+
88+
receiver.async_receive_from(asio::buffer(received), remote,
89+
[&](const std::error_code& ec, std::size_t n) {
90+
udp_failure = ec;
91+
receive_done = !ec && n == datagram.size()
92+
&& std::string(received.data(), n) == datagram;
93+
});
94+
sender.async_send_to(asio::buffer(datagram), receiver.local_endpoint(),
95+
[&](const std::error_code& ec, std::size_t n) {
96+
if (ec) udp_failure = ec;
97+
send_done = !ec && n == datagram.size();
98+
});
99+
100+
udp_io.run();
101+
return !udp_failure && receive_done && send_done ? 0 : 8;
102+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Surface/smoke coverage: exported types, completion tokens, and vocabulary
2+
// types are usable across the module boundary.
3+
import std;
4+
import asio;
5+
6+
int main() {
7+
// --- buffer / address (existing) ---
8+
std::array<char, 4> src{'m', 'c', 'p', 'p'};
9+
asio::const_buffer cb = asio::buffer(src);
10+
if (cb.size() != 4) return 1;
11+
12+
std::string dst(4, '\0');
13+
asio::mutable_buffer mb = asio::buffer(dst);
14+
std::memcpy(mb.data(), cb.data(), cb.size());
15+
if (dst != "mcpp") return 2;
16+
17+
asio::ip::address_v4 loopback = asio::ip::address_v4::loopback();
18+
if (loopback.to_string() != "127.0.0.1") return 3;
19+
20+
asio::ip::tcp::endpoint ep(loopback, 8080);
21+
if (ep.port() != 8080) return 4;
22+
23+
// --- execution context hierarchy ---
24+
if (!std::is_base_of_v<asio::execution_context, asio::io_context>) return 5;
25+
if (!std::is_base_of_v<asio::execution_context, asio::system_context>) return 6;
26+
if (!std::is_base_of_v<asio::execution_context, asio::thread_pool>) return 7;
27+
28+
// --- executors ---
29+
static_assert(std::is_class_v<asio::any_io_executor>);
30+
static_assert(std::is_class_v<asio::system_executor>);
31+
32+
// --- error_code typedef ---
33+
static_assert(std::is_same_v<asio::error_code, std::error_code>);
34+
35+
// --- cancellation_type (scoped enum) ---
36+
static_assert(std::is_enum_v<asio::cancellation_type>);
37+
if (static_cast<int>(asio::cancellation_type::all) == 0) return 8;
38+
if (static_cast<int>(asio::cancellation_type::terminal) == 0) return 9;
39+
40+
// --- signal / timer types ---
41+
static_assert(std::is_class_v<asio::signal_set>);
42+
static_assert(std::is_class_v<asio::system_timer>);
43+
44+
// --- completion token variables ---
45+
asio::io_context surface_io;
46+
// detached — compile test for the constexpr variable and its usage
47+
asio::steady_timer t(surface_io, std::chrono::milliseconds(0));
48+
t.async_wait(asio::detached);
49+
static_assert(std::is_same_v<decltype(asio::detached), const asio::detached_t>);
50+
51+
// use_future — accessible as a named variable
52+
auto uf = asio::use_future;
53+
(void)uf;
54+
55+
// deferred
56+
static_assert(std::is_same_v<decltype(asio::deferred), const asio::deferred_t>);
57+
58+
// --- redirect_error ---
59+
std::error_code redirect_ec;
60+
auto redirected = asio::redirect_error(redirect_ec);
61+
(void)redirected;
62+
63+
// --- bind_executor ---
64+
auto bound = asio::bind_executor(asio::system_executor(), []{});
65+
(void)bound;
66+
67+
// --- associated traits ---
68+
static_assert(std::is_class_v<asio::associated_allocator<int>>);
69+
static_assert(std::is_class_v<asio::associated_executor<int>>);
70+
static_assert(std::is_class_v<asio::associated_cancellation_slot<int>>);
71+
72+
// --- error namespace ---
73+
if (asio::error::operation_aborted == std::error_code{}) return 10;
74+
75+
return 0;
76+
}

0 commit comments

Comments
 (0)