qev is a maintained continuation of libev, with a patched wepoll vendored for Windows. The goal is an event loop that is correct, warning-clean and fast on every platform, while its C API stays exactly libev's.
- The public API is a contract.
ev_*symbols, struct layouts and theevent.hlibevent shim are what consumers compiled against. A change that affects them needs a very good reason, aCHANGELOGentry that says so plainly, and a version bump. - Keep it warning-clean. The tree builds under a strict
-Wall -Wextra -Wpedantic -Wshadow -Wcast-align …set with zero warnings, and passes ASan/UBSan. A PR that adds one is not ready. - Preserve behaviour except for proven bugs, and call out any behaviour change
explicitly — in the PR and in
CHANGELOG.md. - No mass reformatting. The core is libev's, and it keeps libev's style. That is not taste: it is what makes a file-to-file comparison against upstream possible when a future change has to be read or picked up. A reformat destroys that permanently.
- The source layout is upstream's, deliberately. Everything flat, backends included
from
ev.cas one translation unit. The public/internal split a reader wants is delivered by the install —include/qev/ev.hships,ev_vars.hdoes not.
The qb Actor Framework embeds this exact source and ships
it as libqb-ev.a. Two consequences you will run into:
- The same files exist in both repositories and must stay identical. A change here that does not also land in qb's tree will be caught — qb's development superproject runs a byte-identity check over the shared files on every verification pass. Land both sides, or the next person inherits a divergence.
- qb builds a reduced profile. Seven watcher families (idle, prepare, check, fork,
child, async, embed) are compiled out there, so
struct ev_loophas a different layout under qb than in a standalone build. If your change touches anything gated onEV_*_ENABLE, test both:-DQB_EV_WATCHERS_FULL=ONand=OFF. The matrix'sreduced-profilecell does this on every run, so a change that only compiles under the full profile is caught rather than discovered by qb.
Fixes flow both ways. A backend bug surfaced by qb's test suite belongs here; an improvement here reaches qb on its next sync.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release # add -DBUILD_SHARED_LIBS=ON for a shared lib
cmake --build build
ctest --test-dir build --output-on-failure # watcher + loop coverage (+ wepoll on Windows)
./build/qev-bench-backends # backend benchmarkautotools is maintained alongside:
./autogen.sh && ./configure && makeRun the full matrix — CMake static and shared, autotools through make install, a
find_package(qev) consumer in both link modes, ASan/UBSan, every backend, the C++
wrapper, the exported-symbol census, the shared-prefix install check, the reduced
watcher profile qb ships, and a regeneration of ev_wrap.h:
bash scripts/test-build-matrix.sh nativeIn a clean container, which also exercises autotools and io_uring:
docker run --rm --security-opt seccomp=unconfined -v "$PWD":/src:ro ubuntu:24.04 \
bash -c 'bash /src/scripts/test-build-matrix.sh gcc'CI runs the same matrix on Linux (gcc + clang) and macOS, and on Windows/MSVC builds, tests and censuses the archive — every push and PR.
A test that cannot fail is not a test. When you fix a bug, add the case that reproduces it and confirm it fails against the unfixed code before you commit the fix.
- Branch from
develop. (mainis the released line.) - Focused commits, explaining why rather than what.
- A test in
tests/for any bug fixed. - An entry in
CHANGELOG.mdunder the unreleased section. bash scripts/test-build-matrix.sh nativegreen, build warning-free.- Open the PR using the template.
Use the GitHub issue templates for bugs and feature requests. For security vulnerabilities follow SECURITY.md — do not open a public issue.
By contributing you agree that your contributions are licensed under the project's MIT license. Portions derived from libev and wepoll remain under their original BSD-2-Clause terms (see THIRD-PARTY-NOTICES).