diff --git a/.agents/docs/2026-10-02-add-libevent-plan.md b/.agents/docs/2026-10-02-add-libevent-plan.md new file mode 100644 index 00000000..b6ba6eec --- /dev/null +++ b/.agents/docs/2026-10-02-add-libevent-plan.md @@ -0,0 +1,10 @@ +# Libevent 2.1.13 compat package + +- Source: `libevent/libevent`, latest stable tag observed `release-2.1.13-stable` (2.2 tags were alpha prereleases); GitHub tag archive SHA-256 `1a0885e17dc78afbaeddf13cf849f9238bbc24acdc178464a0d1934d7c5ffbd5`, calculated twice. +- Shape: Form B, C source package. The release needs `event2/event-config.h` and `evconfig-private.h` generated by CMake feature probes. `install()` invokes declared `xim:cmake` only to produce those platform headers; mcpp compiles the upstream C translation units so the library follows the consumer's toolchain. Its source list mirrors CMake's `SRC_CORE` and `SRC_EXTRA`, plus the per-platform backends (Linux epoll, macOS kqueue, Windows IOCP/select), and POSIX/Win32 threading. TLS integrations are not part of the default library and are deliberately excluded, not exposed as unverified features. +- Platforms: linux, macosx, windows all declared against the same archive and sha256. The test member executes the same event timer, evbuffer round trip, and HTTP object construction on each. Windows system libraries are explicitly linked. +- License: BSD-3-Clause for the main sources, ISC for arc4random included by `evutil_rand.c`, and MIT for the Windows timer code derived from libutp (`evutil_time.c`; see upstream LICENSE). The separate wepoll and TLS integrations are not compiled. +- Mirror: no GitCode credentials/assets available; use a plain upstream URL (not a fictional CN URL). +- Local Linux verification: mcpp 2026.10.1.2 `mcpp xpkg parse --all-os pkgs/c/compat.libevent.lua` reports 24/24/25 sources; cold `mcpp test -p libevent --cache off` passed under GCC 16.1.0. The test deliberately uses `EVLOOP_ONCE` because `event_base_dispatch()` returns one after a one-shot timer drains the queue even when its callback ran. +- PR #503 initially failed only on Linux default GCC: CMake's host probes produced an incomplete `event-config.h` (missing pointer-size and socket/address-family macros) when using the ambient toolchain wrapper. The hook now selects native `/usr/bin/cc` for CMake's Linux/macOS host probes; mcpp still compiles the sources with the selected consumer compiler. The fix passed a cold local Linux GCC build and the full PR matrix on Linux GCC/LLVM, macOS and Windows. +- The final PR validation run passed lint, mirror checks, build, Linux default, all Linux LLVM shards, macOS, and Windows. Results: [PR #503 checks](https://github.com/mcpplibs/mcpp-index/pull/503/checks). diff --git a/CHANGELOG.md b/CHANGELOG.md index e3120416..0c11e4c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ ### Added +- **`compat.libevent` 2.1.13。** Linux、macOS、Windows 分别通过上游 CMake 生成 + 平台配置头,再由 mcpp 编译 libevent 核心、HTTP/DNS 扩展和对应系统后端; + TLS 可选组件未收录。workspace 成员测试事件分派、缓冲区和 HTTP 对象。 + - **`compat.muduo` 2.0.3 与 Boost 头组件。** muduo 仅支持 Linux;内联构建 `muduo_base`/`muduo_net` 源码,按上游 2.0.3 的依赖补充 `compat.boost-any`、 `compat.boost-circular-buffer`、`compat.boost-concept-check` 并复用既有 `compat.boost-utility`。 diff --git a/README.md b/README.md index 13053cc8..0459ca25 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,7 @@ One descriptor per common shape: | Native module library (Form A) | [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`gzj-creator.galay`](pkgs/g/gzj-creator.galay.lua) | | C sources + `features` | [`compat.cjson`](pkgs/c/compat.cjson.lua) | | C++ sources, several versions | [`compat.yaml-cpp`](pkgs/c/compat.yaml-cpp.lua) | -| Boost header-only components | [`compat.boost-any`](pkgs/c/compat.boost-any.lua) · [`compat.boost-circular-buffer`](pkgs/c/compat.boost-circular-buffer.lua) · [`compat.boost-concept-check`](pkgs/c/compat.boost-concept-check.lua) · [`compat.boost-utility`](pkgs/c/compat.boost-utility.lua) | | Header-only | [`compat.gtl`](pkgs/c/compat.gtl.lua) | -| Linux-only C++ network library | [`compat.muduo`](pkgs/c/compat.muduo.lua) | | Generated config header | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | | C++23 module shipped by upstream | [`khronos.vulkan-hpp`](pkgs/k/khronos.vulkan-hpp.lua) | diff --git a/README.zh-CN.md b/README.zh-CN.md index 3ac1bbb9..a996bda4 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -37,8 +37,6 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 原生模块库(Form A) | [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`gzj-creator.galay`](pkgs/g/gzj-creator.galay.lua) | | C 源码 + `features` | [`compat.cjson`](pkgs/c/compat.cjson.lua) | | C++ 源码,多版本 | [`compat.yaml-cpp`](pkgs/c/compat.yaml-cpp.lua) | -| Boost 头文件组件 | [`compat.boost-any`](pkgs/c/compat.boost-any.lua) · [`compat.boost-circular-buffer`](pkgs/c/compat.boost-circular-buffer.lua) · [`compat.boost-concept-check`](pkgs/c/compat.boost-concept-check.lua) · [`compat.boost-utility`](pkgs/c/compat.boost-utility.lua) | -| 仅 Linux 的 C++ 网络库 | [`compat.muduo`](pkgs/c/compat.muduo.lua) | | header-only | [`compat.gtl`](pkgs/c/compat.gtl.lua) | | 生成 config 头 | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | diff --git a/mcpp.toml b/mcpp.toml index 8046edb1..ad27a9d9 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -107,6 +107,7 @@ members = [ "tests/examples/libassert", "tests/examples/libcoro", "tests/examples/libdrm", + "tests/examples/libevent", "tests/examples/libffi", "tests/examples/expat", "tests/examples/spirv-headers", diff --git a/pkgs/c/compat.libevent.lua b/pkgs/c/compat.libevent.lua new file mode 100644 index 00000000..5a48ccf3 --- /dev/null +++ b/pkgs/c/compat.libevent.lua @@ -0,0 +1,133 @@ +-- libevent 2.1.13-stable: configure headers with upstream CMake, then compile +-- core and extra sources with mcpp's C toolchain on Linux, macOS and Windows. +package = { + spec = "1", + namespace = "compat", + name = "libevent", + description = "Libevent asynchronous event notification and networking library", + licenses = {"BSD-3-Clause", "ISC", "MIT"}, + repo = "https://github.com/libevent/libevent", + type = "package", + + xpm = { + linux = { + deps = { "xim:cmake@latest" }, + ["2.1.13"] = { + url = "https://github.com/libevent/libevent/archive/refs/tags/release-2.1.13-stable.tar.gz", + sha256 = "1a0885e17dc78afbaeddf13cf849f9238bbc24acdc178464a0d1934d7c5ffbd5", + }, + }, + macosx = { + deps = { "xim:cmake@latest" }, + ["2.1.13"] = { + url = "https://github.com/libevent/libevent/archive/refs/tags/release-2.1.13-stable.tar.gz", + sha256 = "1a0885e17dc78afbaeddf13cf849f9238bbc24acdc178464a0d1934d7c5ffbd5", + }, + }, + windows = { + deps = { "xim:cmake@latest" }, + ["2.1.13"] = { + url = "https://github.com/libevent/libevent/archive/refs/tags/release-2.1.13-stable.tar.gz", + sha256 = "1a0885e17dc78afbaeddf13cf849f9238bbc24acdc178464a0d1934d7c5ffbd5", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/include", "config/include", "*/compat", "*/WIN32-Code" }, + cflags = { "-DHAVE_CONFIG_H" }, + sources = { + "*/buffer.c", "*/bufferevent.c", "*/bufferevent_filter.c", + "*/bufferevent_pair.c", "*/bufferevent_ratelim.c", + "*/bufferevent_sock.c", "*/event.c", "*/evmap.c", + "*/evthread.c", "*/evutil.c", "*/evutil_rand.c", + "*/evutil_time.c", "*/listener.c", "*/log.c", + "*/signal.c", "*/strlcpy.c", "*/event_tagging.c", + "*/http.c", "*/evdns.c", "*/evrpc.c", + }, + targets = { ["event"] = { kind = "lib" } }, + deps = {}, + linux = { + sources = { "*/select.c", "*/poll.c", "*/epoll.c", "*/evthread_pthread.c" }, + }, + macosx = { + sources = { "*/select.c", "*/poll.c", "*/kqueue.c", "*/evthread_pthread.c" }, + }, + windows = { + sources = { "*/buffer_iocp.c", "*/bufferevent_async.c", "*/event_iocp.c", + "*/win32select.c", "*/evthread_win32.c" }, + cflags = { "-D_CRT_SECURE_NO_WARNINGS", "-D_CRT_NONSTDC_NO_DEPRECATE" }, + ldflags = { "-lws2_32", "-lshell32", "-ladvapi32", "-liphlpapi" }, + }, + }, +} + +import("xim.libxpkg.pkginfo") +import("xim.libxpkg.log") + +local function quote(s) + return '"' .. tostring(s):gsub('"', '\\"') .. '"' +end + +function install() + local wrap = "libevent-release-" .. pkginfo.version() .. "-stable" + if not os.isfile(path.join(wrap, "CMakeLists.txt")) then + log.error("compat.libevent: expected %s/CMakeLists.txt", wrap) + return false + end + local prefix = pkginfo.install_dir() + os.tryrm(prefix) + os.mkdir(prefix) + local srcroot = path.join(prefix, wrap) + os.mv(wrap, srcroot) + + local pkg = pkginfo.build_dep("xim:cmake") + local cmake + if pkg then + for _, candidate in ipairs({ + pkg.bin and path.join(pkg.bin, "cmake") or "", + pkg.bin and path.join(pkg.bin, "cmake.exe") or "", + pkg.path and path.join(pkg.path, "cmake.app/Contents/bin/cmake") or "", + pkg.path and path.join(pkg.path, "CMake.app/Contents/bin/cmake") or "", + }) do + if os.isfile(candidate) then cmake = candidate; break end + end + end + if not cmake then + log.error("compat.libevent: xim:cmake executable not found") + return false + end + local command = quote(cmake) .. " -S " .. quote(srcroot) + .. " -B " .. quote(path.join(prefix, "config")) + .. " -DCMAKE_POLICY_VERSION_MINIMUM=3.5" + -- Probe the native platform with its system compiler, not a toolchain + -- wrapper whose sysroot may differ from the generated headers' ABI. + .. (os.host() == "windows" and "" or " -DCMAKE_C_COMPILER=/usr/bin/cc") + .. " -DEVENT__DISABLE_OPENSSL=ON -DEVENT__DISABLE_MBEDTLS=ON" + .. " -DEVENT__DISABLE_TESTS=ON -DEVENT__DISABLE_SAMPLES=ON" + .. " -DEVENT__DISABLE_BENCHMARK=ON -DEVENT__LIBRARY_TYPE=STATIC" + local logfile = path.join(prefix, "mcpp_cmake_configure.log") + if os.host() == "windows" then + -- Windows xlings does not reliably execute a quoted .exe directly. + -- Drive it through cmd as compat.openssl does, and inspect its output. + local bat = path.join(prefix, "mcpp_configure.bat") + io.writefile(bat, "@echo off\r\n" .. command .. " > " .. quote(logfile) + .. " 2>&1\r\nif errorlevel 1 exit /b 1\r\n") + command = "cmd /c " .. quote(bat) + end + local ok, result = pcall(os.exec, command) + if not ok or not result then + log.error("compat.libevent: CMake configuration failed: %s; log: %s", + tostring(result), os.isfile(logfile) and io.readfile(logfile) or "") + return false + end + local generated = path.join(prefix, "config/include/event2/event-config.h") + if not os.isfile(generated) then + log.error("compat.libevent: CMake did not generate %s", generated) + return false + end + return true +end diff --git a/tests/examples/libevent/mcpp.toml b/tests/examples/libevent/mcpp.toml new file mode 100644 index 00000000..a5ef217e --- /dev/null +++ b/tests/examples/libevent/mcpp.toml @@ -0,0 +1,6 @@ +[package] +name = "libevent-tests" +version = "0.1.0" + +[dependencies.compat] +libevent = "2.1.13" diff --git a/tests/examples/libevent/tests/event.cpp b/tests/examples/libevent/tests/event.cpp new file mode 100644 index 00000000..aa399a4e --- /dev/null +++ b/tests/examples/libevent/tests/event.cpp @@ -0,0 +1,61 @@ +#ifdef _WIN32 +#include +#endif + +#include +#include +#include + +#include +#include + +static void fired(evutil_socket_t, short, void* arg) { + ++*static_cast(arg); +} + +int main() { +#ifdef _WIN32 + WSADATA winsock; + if (WSAStartup(MAKEWORD(2, 2), &winsock) != 0) return 1; +#endif + event_base* base = event_base_new(); + if (!base) { +#ifdef _WIN32 + WSACleanup(); +#endif + return 1; + } + int count = 0; + timeval delay = {0, 0}; + const int scheduled = event_base_once(base, -1, EV_TIMEOUT, fired, &count, &delay); + const int dispatched = scheduled == 0 ? event_base_loop(base, EVLOOP_ONCE) : -1; + bool ok = scheduled == 0 && dispatched == 0 && count == 1; + if (!ok) std::fprintf(stderr, "event loop: schedule=%d dispatch=%d count=%d\n", scheduled, dispatched, count); + + evbuffer* buffer = evbuffer_new(); + if (!buffer) { + event_base_free(base); +#ifdef _WIN32 + WSACleanup(); +#endif + return 1; + } + const char text[] = "libevent"; + char out[sizeof(text)] = {}; + const bool buffer_ok = evbuffer_add(buffer, text, sizeof(text) - 1) == 0 + && evbuffer_remove(buffer, out, sizeof(text) - 1) == sizeof(text) - 1 + && std::strcmp(out, text) == 0 && evbuffer_get_length(buffer) == 0; + if (!buffer_ok) std::fprintf(stderr, "buffer round trip failed\n"); + ok = ok && buffer_ok; + + evhttp* http = evhttp_new(base); + ok = ok && http != nullptr; + if (!http) std::fprintf(stderr, "evhttp_new failed\n"); + if (http) evhttp_free(http); + evbuffer_free(buffer); + event_base_free(base); +#ifdef _WIN32 + WSACleanup(); +#endif + return ok ? 0 : 1; +}