From e087a4089bfa388c9ddd5ebf8c5bbb2cb1bac5bb Mon Sep 17 00:00:00 2001 From: wellwei Date: Fri, 17 Jul 2026 15:23:35 +0800 Subject: [PATCH 1/6] feat(pkgs): add standalone asio 1.38.1 --- README.md | 4 +- mcpp.toml | 1 + pkgs/c/compat.asio.lua | 69 ++++++++++++++ tests/examples/asio/mcpp.toml | 10 ++ tests/examples/asio/tests/core.cpp | 72 ++++++++++++++ tests/examples/asio/tests/coroutine.cpp | 34 +++++++ tests/examples/asio/tests/experimental.cpp | 42 +++++++++ tests/examples/asio/tests/network.cpp | 103 +++++++++++++++++++++ tests/examples/asio/tests/platform.cpp | 42 +++++++++ tests/examples/asio/tests/surface.cpp | 84 +++++++++++++++++ 10 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 pkgs/c/compat.asio.lua create mode 100644 tests/examples/asio/mcpp.toml create mode 100644 tests/examples/asio/tests/core.cpp create mode 100644 tests/examples/asio/tests/coroutine.cpp create mode 100644 tests/examples/asio/tests/experimental.cpp create mode 100644 tests/examples/asio/tests/network.cpp create mode 100644 tests/examples/asio/tests/platform.cpp create mode 100644 tests/examples/asio/tests/surface.cpp diff --git a/README.md b/README.md index 53609c1..729e23e 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 用户基于 mcpp 开发并登记进索引的库(如 `tensorvia-cpu`)。其上游通常自带 `mcpp.toml`,描述文件(Form A)只声明 元数据与下载地址。 - **第三方 C/C++ 库(`compat`)**:其上游不提供 mcpp 支持,描述文件(Form B)内联构建信息。该类库存在 - header-only、纯 C 源码、C++23 module wrapper 等形态,可选组件经 `features` 门控,并配备 GitCode CN 镜像。 + header-only、纯 C 源码、C++23 module wrapper 等形态,可选组件经 `features` 门控;具备镜像资产的包同时提供 GitCode CN 镜像。 ### 参考示例(`.lua` 描述符) @@ -34,7 +34,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 |------|------| | 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) | | C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) | -| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | +| header-only(含 public 配置/系统链接) | [`compat.eigen`](pkgs/c/compat.eigen.lua) · [`compat.asio`](pkgs/c/compat.asio.lua) | | 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.opencv`](pkgs/c/compat.opencv.lua)(CMake) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) | 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..1c9f91b --- /dev/null +++ b/pkgs/c/compat.asio.lua @@ -0,0 +1,69 @@ +-- 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 = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz", + sha256 = "2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc", + }, + }, + macosx = { + ["1.38.1"] = { + url = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz", + sha256 = "2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc", + }, + }, + windows = { + ["1.38.1"] = { + url = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz", + sha256 = "2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc", + }, + }, + }, + + 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. + features = { + ["default"] = { implies = { "standalone" } }, + ["standalone"] = { + defines = { + "ASIO_STANDALONE", + "ASIO_HEADER_ONLY", + "ASIO_DISABLE_BOOST_CONTEXT_FIBER", + }, + }, + }, + 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; +} From 4dcdefbbd326ef273cb302832c511743f2e47c88 Mon Sep 17 00:00:00 2001 From: wellwei Date: Fri, 17 Jul 2026 19:25:49 +0800 Subject: [PATCH 2/6] docs(asio): record header-only package validation plan --- .agents/docs/2026-07-17-add-asio-plan.md | 154 +++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 .agents/docs/2026-07-17-add-asio-plan.md 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..6c7fd8b --- /dev/null +++ b/.agents/docs/2026-07-17-add-asio-plan.md @@ -0,0 +1,154 @@ +# 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. +- Archive URL: + `https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz`. +- Archive SHA-256: + `2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc`. + +Two independent downloads produced the same SHA-256. The archive is wrapped in +`asio-asio-1-38-1/`, and its public entry header is +`asio-asio-1-38-1/include/asio.hpp`. Therefore `*/include` is the required +consumer include root. + +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, macOS, and Windows use the same portable source archive; +- 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`. + +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 + +No authorized, byte-identical `mcpp-res` CN asset is available for this +contribution. The descriptor therefore uses the plain canonical upstream URL, +which is the current repository's supported fallback. It does not fabricate a +CN entry or alias the upstream URL as a CN mirror. + +A maintainer may add a legitimate CN mirror later by uploading the exact same +archive bytes and retaining the pinned SHA-256. + +## 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 Lua 5.4; +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 `mcpp test --workspace` on the local host; +7. 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; +- descriptor grammar and exact Lua 5.4 lint pass; +- the CLI dependency token is verified through an isolated `mcpp add`; +- the six targeted consumer tests pass from isolated build state; +- the local workspace test passes; +- 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. From df145c08fcbe53d3a6108310ade86ea7b7f34cbd Mon Sep 17 00:00:00 2001 From: wellwei Date: Fri, 17 Jul 2026 19:44:21 +0800 Subject: [PATCH 3/6] docs(asio): keep contribution scope focused --- .agents/docs/2026-07-17-add-asio-plan.md | 9 ++++----- README.md | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.agents/docs/2026-07-17-add-asio-plan.md b/.agents/docs/2026-07-17-add-asio-plan.md index 6c7fd8b..1e45bbc 100644 --- a/.agents/docs/2026-07-17-add-asio-plan.md +++ b/.agents/docs/2026-07-17-add-asio-plan.md @@ -114,14 +114,13 @@ The tests do not include or exercise the separate native module adapter. 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 Lua 5.4; +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 `mcpp test --workspace` on the local host; -7. run `git diff --check` and confirm README is identical to `origin/main`. +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 @@ -144,10 +143,10 @@ The replacement PR is limited to: ## 8. Acceptance criteria - upstream version, license, layout, and repeated archive digest are recorded; -- descriptor grammar and exact Lua 5.4 lint pass; +- 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 local workspace test passes; - README has no diff; - the replacement PR contains no native module adaptation; - all required GitHub Actions jobs pass before maintainer merge; diff --git a/README.md b/README.md index 729e23e..53609c1 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 用户基于 mcpp 开发并登记进索引的库(如 `tensorvia-cpu`)。其上游通常自带 `mcpp.toml`,描述文件(Form A)只声明 元数据与下载地址。 - **第三方 C/C++ 库(`compat`)**:其上游不提供 mcpp 支持,描述文件(Form B)内联构建信息。该类库存在 - header-only、纯 C 源码、C++23 module wrapper 等形态,可选组件经 `features` 门控;具备镜像资产的包同时提供 GitCode CN 镜像。 + header-only、纯 C 源码、C++23 module wrapper 等形态,可选组件经 `features` 门控,并配备 GitCode CN 镜像。 ### 参考示例(`.lua` 描述符) @@ -34,7 +34,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 |------|------| | 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) | | C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) | -| header-only(含 public 配置/系统链接) | [`compat.eigen`](pkgs/c/compat.eigen.lua) · [`compat.asio`](pkgs/c/compat.asio.lua) | +| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | | 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.opencv`](pkgs/c/compat.opencv.lua)(CMake) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) | From 8da9ade65edf2d6c124337071c7aabb79d1d42a3 Mon Sep 17 00:00:00 2001 From: wellwei Date: Fri, 17 Jul 2026 20:58:27 +0800 Subject: [PATCH 4/6] fix(asio): use zip archive on Windows --- .agents/docs/2026-07-17-add-asio-plan.md | 23 ++++++++++++++++------- pkgs/c/compat.asio.lua | 7 +++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.agents/docs/2026-07-17-add-asio-plan.md b/.agents/docs/2026-07-17-add-asio-plan.md index 1e45bbc..a82210e 100644 --- a/.agents/docs/2026-07-17-add-asio-plan.md +++ b/.agents/docs/2026-07-17-add-asio-plan.md @@ -31,16 +31,23 @@ consumer contract and requires its own compatibility evidence. - Tag commit: `bbecff21a23b97c34641f0f1f08b28c91b9c77cf`. - License: Boost Software License 1.0 (`BSL-1.0`), confirmed from upstream `COPYING` and `LICENSE_1_0.txt` at the tag. -- Archive URL: - `https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz`. -- Archive SHA-256: +- Linux/macOS archive: tag tarball, SHA-256 `2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc`. +- Windows archive: the same tag's ZIP encoding, SHA-256 + `c4557a5a07ff8aa9c37bd141b7d1a6ba2b1bad5557d97762ad27aaf0091c665b`. -Two independent downloads produced the same SHA-256. The archive is wrapped in -`asio-asio-1-38-1/`, and its public entry header is +Two independent downloads of each archive encoding produced the same +platform-specific SHA-256. Both 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. +The tag tarball also contains `asio/include -> ../include` and +`asio/src -> ../src` POSIX symlinks. The Windows xlings extraction path exited +with code 127 immediately after downloading that tarball. The Windows entry +therefore uses GitHub's ZIP encoding of the same tagged commit, following the +repository's existing platform-specific archive pattern. + The upstream tag is annotated but not cryptographically signed. Reproducibility is enforced by the descriptor's pinned archive digest. @@ -53,7 +60,8 @@ so the package uses an inline Form B descriptor at - namespace: `compat`; - full package name: `compat.asio`; - published version: bare version `1.38.1`; -- platforms: Linux, macOS, and Windows use the same portable source archive; +- platforms: Linux and macOS use the tag tarball; Windows uses the tag ZIP to + avoid the tarball's POSIX symlink extraction failure; - include root: `*/include`; - build target: a generated C anchor provides the buildable library target required by the current package resolver; @@ -88,7 +96,7 @@ which is the current repository's supported fallback. It does not fabricate a CN entry or alias the upstream URL as a CN mirror. A maintainer may add a legitimate CN mirror later by uploading the exact same -archive bytes and retaining the pinned SHA-256. +bytes for each platform archive and retaining its pinned SHA-256. ## 5. Consumer and test design @@ -147,6 +155,7 @@ The replacement PR is limited to: 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; diff --git a/pkgs/c/compat.asio.lua b/pkgs/c/compat.asio.lua index 1c9f91b..922c6d9 100644 --- a/pkgs/c/compat.asio.lua +++ b/pkgs/c/compat.asio.lua @@ -25,8 +25,11 @@ package = { }, windows = { ["1.38.1"] = { - url = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz", - sha256 = "2827b229972be80cdb14e5497962fa393d1adf036b5869e2b9c99f644daadacc", + -- The tag tarball contains two POSIX symlinks. xlings cannot + -- materialize them on the Windows runner, so use GitHub's ZIP + -- encoding of the same tagged commit on this platform. + url = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.zip", + sha256 = "c4557a5a07ff8aa9c37bd141b7d1a6ba2b1bad5557d97762ad27aaf0091c665b", }, }, }, From 3bb23bd8b4bcbf2df33ee04a460e3c996010fdb9 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 17 Jul 2026 21:48:58 +0800 Subject: [PATCH 5/6] fix(asio): symlink-free windows archive + CN mirrors Upstream's tag archives (tar.gz AND zip) both contain the POSIX symlinks asio/include -> ../include and asio/src -> ../src, so switching encodings could not fix the Windows extraction failure (tar.exe cannot materialize them; job exits 127 right after download). Upstream publishes no symlink-free asset for 1.38.x (no GitHub release assets; SourceForge stops at 1.36.0). Windows now 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 byte-identical, provenance in xlings-res/asio README), hosted at github.com/xlings-res/asio (GLOBAL) and gitcode.com/mcpp-res/asio (CN). Linux/macOS keep the canonical upstream tarball as GLOBAL and gain a byte-identical CN mirror on mcpp-res. All hosted assets sha256-verified after upload. Verified locally with pinned mcpp 0.0.94: descriptor lints + xpkg parse pass, and the asio member test passes cold (6/6). --- .agents/docs/2026-07-17-add-asio-plan.md | 52 ++++++++++++++---------- pkgs/c/compat.asio.lua | 27 ++++++++---- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/.agents/docs/2026-07-17-add-asio-plan.md b/.agents/docs/2026-07-17-add-asio-plan.md index a82210e..ee07d51 100644 --- a/.agents/docs/2026-07-17-add-asio-plan.md +++ b/.agents/docs/2026-07-17-add-asio-plan.md @@ -32,21 +32,28 @@ consumer contract and requires its own compatibility evidence. - 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`. -- Windows archive: the same tag's ZIP encoding, SHA-256 - `c4557a5a07ff8aa9c37bd141b7d1a6ba2b1bad5557d97762ad27aaf0091c665b`. - -Two independent downloads of each archive encoding produced the same -platform-specific SHA-256. Both 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 + `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. -The tag tarball also contains `asio/include -> ../include` and -`asio/src -> ../src` POSIX symlinks. The Windows xlings extraction path exited -with code 127 immediately after downloading that tarball. The Windows entry -therefore uses GitHub's ZIP encoding of the same tagged commit, following the -repository's existing platform-specific archive pattern. +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. @@ -60,8 +67,8 @@ so the package uses an inline Form B descriptor at - 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 tag ZIP to - avoid the tarball's POSIX symlink extraction failure; +- 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; @@ -90,13 +97,16 @@ workflow pin (`MCPP_VERSION = "0.0.94"`). ## 4. URL and mirror decision -No authorized, byte-identical `mcpp-res` CN asset is available for this -contribution. The descriptor therefore uses the plain canonical upstream URL, -which is the current repository's supported fallback. It does not fabricate a -CN entry or alias the upstream URL as a CN mirror. +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). -A maintainer may add a legitimate CN mirror later by uploading the exact same -bytes for each platform archive and retaining its pinned SHA-256. +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 diff --git a/pkgs/c/compat.asio.lua b/pkgs/c/compat.asio.lua index 922c6d9..41745b1 100644 --- a/pkgs/c/compat.asio.lua +++ b/pkgs/c/compat.asio.lua @@ -13,23 +13,36 @@ package = { xpm = { linux = { ["1.38.1"] = { - url = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz", + 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 = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.tar.gz", + 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"] = { - -- The tag tarball contains two POSIX symlinks. xlings cannot - -- materialize them on the Windows runner, so use GitHub's ZIP - -- encoding of the same tagged commit on this platform. - url = "https://github.com/chriskohlhoff/asio/archive/refs/tags/asio-1-38-1.zip", - sha256 = "c4557a5a07ff8aa9c37bd141b7d1a6ba2b1bad5557d97762ad27aaf0091c665b", + -- 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", }, }, }, From 2208efccc30dabb343f7f928cb78b38854da39b6 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 17 Jul 2026 22:11:19 +0800 Subject: [PATCH 6/6] fix(asio): pin ASIO_HAS_THREADS for the llvm-on-Windows toolchain With the symlink-free archive in place, the Windows job progressed to runtime failures: core and network crashed with 0xC0000409 and coroutine exited 1 while the thread-free tests (experimental/platform/surface) passed. Reproduced under mingw+wine: asio's thread detection keys off CRT macros (_MT/_REENTRANT/_POSIX_THREADS) the toolchain does not define, so it silently selects null_thread and the steady_timer wait thread throws operation_not_supported (10045, 'thread: asio.system error'). Pin ASIO_HAS_THREADS in the default standalone feature. asio only tests defined(ASIO_HAS_THREADS) and POSIX pthread selection still runs beneath it, so this is a no-op where detection already works. All six consumer tests pass under mingw+wine with the pin (platform's pipe path is a wine limitation, passes on real Windows), and the Linux member suite passes 6/6 cold with the define present in every consumer TU's compile command. --- .agents/docs/2026-07-17-add-asio-plan.md | 13 ++++++++++++- pkgs/c/compat.asio.lua | 10 ++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/.agents/docs/2026-07-17-add-asio-plan.md b/.agents/docs/2026-07-17-add-asio-plan.md index ee07d51..8087396 100644 --- a/.agents/docs/2026-07-17-add-asio-plan.md +++ b/.agents/docs/2026-07-17-add-asio-plan.md @@ -84,7 +84,18 @@ The default `standalone` feature contributes these public preprocessor defines: - `ASIO_STANDALONE`; - `ASIO_HEADER_ONLY`; -- `ASIO_DISABLE_BOOST_CONTEXT_FIBER`. +- `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 diff --git a/pkgs/c/compat.asio.lua b/pkgs/c/compat.asio.lua index 41745b1..1f0bc63 100644 --- a/pkgs/c/compat.asio.lua +++ b/pkgs/c/compat.asio.lua @@ -63,6 +63,15 @@ int mcpp_compat_asio_headers_anchor(void) { return 0; } 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"] = { @@ -70,6 +79,7 @@ int mcpp_compat_asio_headers_anchor(void) { return 0; } "ASIO_STANDALONE", "ASIO_HEADER_ONLY", "ASIO_DISABLE_BOOST_CONTEXT_FIBER", + "ASIO_HAS_THREADS", }, }, },