Repository navigation
Full SWIG wrap: DCContext managers, EisPyContext, listener directors - #11
Merged
Merged
Conversation
- Point FetchContent to github.com/transfix/eiskaltdcpp qt6-migration branch - Bump C++ standard from 17 to 20 (required by qt6-migration) - Add USE_QT6=OFF to disable Qt6 GUI (we only need the core library) - Patch CMAKE_SOURCE_DIR/CMAKE_BINARY_DIR -> CMAKE_CURRENT_* for FetchContent compatibility (upstream uses top-level project paths) - Disable eiskaltdcpp's own BUILD_TESTS to avoid leaking from parent - Migrate from Singleton::getInstance() to DCContext accessor pattern: Manager::getInstance() -> getContext()->getManager() (dcpp managers no longer inherit from Singleton<T>) - Use FetchContent_Populate + manual sed patch + add_subdirectory instead of FetchContent_MakeAvailable for patch control
- Use -DUSE_SYSTEM_EISKALTDCPP=OFF so CI builds against the qt6-migration branch via FetchContent instead of the old system-installed libeiskaltdcpp (which lacks the new DCContext/getContext() API) - Add ubuntu-24.04 GitHub-hosted runner alongside the self-hosted runner in the build matrix - Replace libeiskaltdcpp-dev with the actual build deps needed by FetchContent (miniupnpc, idn2, pcre2, lua) - Self-hosted runner failures are non-blocking (continue-on-error)
- Add build-and-test-windows job (MSVC, vcpkg, Ninja, SWIG via choco) - Add build-and-test-macos job (brew deps, openssl@3) - Add Windows and macOS wheel builds via cibuildwheel (delvewheel, delocate) - Add macOS ARM64 + x86_64 wheel matrix entries - Fix CMakeLists.txt: replace sed -i with portable file(READ)/string(REPLACE) - Fix swig/CMakeLists.txt: portable .so/.pyd suffix, Windows copy vs symlinks - Update pyproject.toml OS classifiers for Windows and macOS - Switch sdist/publish jobs to GitHub-hosted ubuntu-24.04 runners
- FreeBSD via vmactions/freebsd-vm (pkg) - OpenBSD via vmactions/openbsd-vm (pkg_add) - NetBSD via vmactions/netbsd-vm (pkg_add) - DragonflyBSD via vmactions/dragonflybsd-vm (pkg) - OmniOS/illumos via vmactions/omnios-vm (pkg) for Solaris compatibility - All BSD/Solaris jobs are continue-on-error (experimental) - Add BSD and Solaris OS classifiers to pyproject.toml
- Haiku: cross-compile using haiku/cross-compiler:x86_64-r1beta5 Docker image - GNU/Hurd: cross-compile using i686-linux-gnu toolchain as approximation - Both are continue-on-error, build-only (no tests), warnings on failure - Added to integration job dependency chain
- Haiku: use x86_64-r1beta4 Docker image (r1beta5 doesn't exist) - Hurd: embed Python3 paths in toolchain file, use BOTH for find modes - FreeBSD: pin release 14.2, explicit python311/py311-pip, libidn2-0, ninja-build - OpenBSD: pin release 7.6, explicit python-3.11.10, lua-5.4.7 package names - NetBSD: pin release 10.1, drop /usr/sbin prefix, remove --break-system-packages - DragonflyBSD: pin release 6.4.0, explicit python311 package - OmniOS: bump timeout to 45 minutes - macOS: add CMAKE_PREFIX_PATH for all brew-installed deps
eiskaltdcpp's custom FindGettext.cmake requires xgettext, msgfmt, etc. All platform builds were failing because these tools were missing. - Ubuntu: add 'gettext' package (both build-and-test and integration jobs) - Windows: add 'gettext' to vcpkg install - macOS: add 'gettext' to brew install + CMAKE_PREFIX_PATH - FreeBSD/DragonflyBSD: add 'gettext-tools' to pkg install - OpenBSD/NetBSD: add 'gettext-tools' to pkg_add - OmniOS: add 'text/gnu-gettext' - Haiku/Hurd cross-compile: add 'gettext' to apt-get install - Wheels: add 'gettext-devel' to manylinux dnf install
Apple Clang lacks std::jthread which eiskaltdcpp's Thread.h requires. Switch to Homebrew GCC (auto-detected version) for both CI and wheels. Also add gettext and gcc to macOS brew install, update CMAKE_PREFIX_PATH.
- macOS: export CPATH/LIBRARY_PATH so Homebrew GCC finds brew headers/libs (GCC doesn't search /opt/homebrew by default unlike Apple Clang) - Haiku: add 'python3' package alongside python3-dev in container - Hurd: add 'python3' package alongside python3-dev - Wheels: add CPATH/LIBRARY_PATH to CIBW_ENVIRONMENT_MACOS
FreeBSD: libidn2-0 → libidn2, ninja-build → ninja, drop unneeded gcc OpenBSD: python-3.11.10 → python%3.11, lua-5.4.7 → lua%5.4, drop bzip2 (in base) NetBSD: use /usr/sbin/pkg_add, drop miniupnpc, add gcc-13 compiler flags DragonflyBSD: add gcc13 for C++20, add git OmniOS: developer/build/cmake → ooce/developer/cmake, drop runtime/lua (N/A), add ooce/developer/ninja + git, use gcc14 from /opt/gcc-14/bin Remove Haiku and Hurd cross-compilation jobs — will add native runners later.
Remove FreeBSD, OpenBSD, NetBSD, DragonflyBSD, and OmniOS CI jobs — the vmactions VMs are too fragile for now; will revisit with native runners later. Fix Windows gettext: install CLI tools (xgettext, msgfmt, etc.) from MSYS2 pacman since vcpkg only provides libintl for linking. Add C:\msys64\usr\bin to PATH so FindGettext.cmake can find them.
- Remove redundant actions/cache on C:/vcpkg/installed — the native VCPKG_BINARY_SOURCES=x-gha caching is more efficient per-package. - wheels.yml: add MSYS2 gettext tools install in CIBW_BEFORE_ALL_WINDOWS, add C:\msys64\usr\bin to PATH and VCPKG_BINARY_SOURCES to CIBW_ENVIRONMENT_WINDOWS for proper caching inside cibuildwheel.
…acts - bridge.cpp: wrap dlfcn.h/unistd.h in #ifndef _WIN32; use <process.h>/_getpid() and <windows.h> on Windows. Skip dlsym-based Lua runtime resolution on Windows (MSVC uses different name mangling; Itanium ABI symbols not available). - ci.yml: upload build artifacts with naming convention eiskaltdcpp-py-<version>~dev-<sha>-<os>-<arch>-py<pyver> so PR artifacts are clearly marked as unreleased. - wheels.yml: rename wheel/sdist artifacts to match naming convention. Tag builds omit '~dev'; non-tag builds include it.
…ault description - Update version from 2.4.2 to 2.5.0.0 in CMakeLists.txt, pyproject.toml, __init__.py, and bridge.cpp - Default nick changed from 'dcpy-<pid>' to 'eispy-<pid>' - Add default description '<username>@<hostname>' for new connections - Add eispy NMDC/ADC client tags - Update test assertions for new nick prefix
BridgeListeners was the last Meyer's singleton in either codebase. Now constructed with DCBridge& reference and owned as a unique_ptr member of DCBridge, consistent with the singleton-free architecture in eiskaltdcpp. Changes: - bridge_listeners.h: remove static getInstance(), add explicit constructor taking DCBridge&, store reference instead of pointer, remove setBridge(), simplify attach() signature - bridge.h: add forward decl + unique_ptr<BridgeListeners> member - bridge.cpp: replace all 7 BridgeListeners::getInstance() call sites with m_listeners-> access - bridge_listeners.cpp: replace m_bridge-> with bridge_., remove null-pointer guard checks (reference always valid)
On Windows with LUA_SCRIPT defined, resolveLuaStatePtr() (which uses dlsym/RTLD_DEFAULT) is unavailable -- only defined under "#if defined(LUA_SCRIPT) && not Win32". Four call sites used bare "#ifdef LUA_SCRIPT", causing C3861 identifier-not-found on MSVC. Change all four guards to match the function definition.
- dcpp::startup() now returns std::unique_ptr<DCContext> - DCBridge stores m_context member (owns the dcpp core context) - shutdown() calls m_context->shutdown() + reset + setContext(nullptr) - Forward-declare dcpp::DCContext in bridge.h - Add #include <dcpp/DCContext.h> to bridge.cpp - All tests pass
- Replace removed SETTING() macro with direct m_context->getSettingsManager()->get() calls (DOWNLOAD_DIRECTORY, TOTAL_DOWNLOAD, TOTAL_UPLOAD) - Pass DCContext& to DirectoryListing constructor and getUserFromFilename() static (now requires context) - Add listener detach loop at start of shutdown() to prevent segfault from socket-thread callbacks firing on stale Client pointers during disconnect - Add FULL_SWIG_WRAP_PLAN.md to .gitignore
Phase 1 of the full SWIG wrap plan — 20 new modular .i interface files that expose all dcpp manager classes directly to Python via SWIG. New files: - dcpp_types.i: CID, TTHValue, User/UserPtr, HintedUser, Identity, HubEntry, FavoriteHubEntry, UserCommand, STL container templates - dcpp_context.i: DCContext, ContextAware, startup/shutdown helpers, forward-declared TimerManager/ResourceManager - dcpp_settings.i: SettingsManager with all setting enums - dcpp_client_manager.i: Hub/user operations, messaging, search - dcpp_search_manager.i: Search modes and operations - dcpp_queue_manager.i: Queue CRUD, priority, Python context manager - dcpp_share_manager.i: Share directory management and stats - dcpp_hash_manager.i: Hashing control and TTH operations - dcpp_download_manager.i: Download count and speed - dcpp_upload_manager.i: Upload stats, slots, waiting users - dcpp_throttle_manager.i: Bandwidth limit controls - dcpp_favorite_manager.i: Favorites CRUD (hubs/users/dirs/commands) - dcpp_log_manager.i: Logging areas, messages, paths - dcpp_finished_manager.i: Finished transfer management - dcpp_crypto_manager.i: TLS and certificate operations - dcpp_connectivity.i: ConnectivityManager + MappingManager - dcpp_ipfilter.i: IP filtering rules (global namespace) - dcpp_adl_search.i: ADL search patterns and manager - dcpp_debug_manager.i: Debug message directions - eispy_context.i: DCBridge Python property accessors for all managers Modified: - dc_core.i: Added stdinc.h preamble and %include directives for all modular .i files Key patterns: - Cherry-pick public methods (no %include of full headers) - %nodefaultctor on all ContextAware subclasses (DCContext& ctor) - %extend for __str__/__repr__/__hash__/__eq__ and Python properties - %nodefaultdtor on DCContext (prevent accidental deletion) All 529 existing tests pass.
Phase 2 of the full SWIG wrap plan — Python Client Layer + REST API. DCClient/AsyncDCClient manager properties: - 16 new @Property accessors: settings, clients, queue, shares, search_manager, downloads, uploads, favorites, finished, hashing, throttle, connectivity, crypto, logs, ip_filter, adl_search - Delegate through DCBridge → DCContext → C++ manager instances SWIG include order fix: - Split dcpp_context.i into dcpp_context_base.i (ContextAware mixin, included before managers) and dcpp_context.i (DCContext class with typed manager accessors, included after all manager definitions) - Fixes SwigPyObject return type for getSettingsManager() etc. - Added IPFilter accessor via %extend on DCContext REST API expansion (8 new route modules): - favorites.py: GET/POST/DELETE hubs + dirs - throttle.py: GET/PUT bandwidth limits (MAX_UPLOAD/DOWNLOAD_SPEED_MAIN) - connectivity.py: GET status, POST detect/setup - crypto.py: GET status, POST certificate generate/reload - logs.py: GET path by area - finished.py: DELETE downloads/uploads - ipfilter.py: POST check IP - adl.py: GET/POST/DELETE searches Pydantic models for all new endpoints (~200 lines). Routes registered in routes/__init__.py. Tests: - test_swig_managers.py: 108 tests for SWIG types, manager properties - test_rest_phase2.py: 23 tests for all new REST endpoints - All 660 tests pass (131 new + 529 existing)
Phase 3 of the full SWIG wrap plan — Remove DCBridge.
EisPyContext replaces DCBridge as the main Python-facing class:
- New src/eispy_context.h/cpp with trimmed API (~830 LOC vs 1445)
- Deleted src/bridge.h/cpp
- 'using DCBridge = EisPyContext;' C++ alias for backward compat
- '%pythoncode { DCBridge = EisPyContext }' Python alias in SWIG
15 pass-through methods removed (now direct SWIG manager access):
- Queue: removeFromQueue, moveQueueItem, setPriority
- Sharing: removeShareDir, renameShareDir, listShare, refreshShare,
getShareSize, getSharedFileCount
- Transfers: getTransferStats
- Hashing: getHashStatus, pauseHashing
- Settings: getSetting, setSetting, reloadConfig
DCClient migration:
- Constructor uses dc_core.EisPyContext()
- Removed methods delegate to self.queue, self.shares, self.hashing,
self.settings (direct SWIG manager properties from Phase 2)
- return-bool wrappers added for share ops (try/except around void)
Updated files:
- bridge_listeners.h/cpp: DCBridge& → EisPyContext&
- src/CMakeLists.txt: source file references
- swig/dc_core.i: class rename, docstrings, removed properties
- swig/eispy_context.i: updated includes and class references
- tests: EisPyContext usage, alias verification, removed-method checks
Create Python-subclassable listener adapter classes for all 7 dcpp listener interfaces. Each adapter inherits from the dcpp listener, overrides tag-dispatched on() methods, and dispatches to named virtual methods (onConnected, onFailed, onAdded, etc.) that SWIG directors expose to Python. Adapter classes (src/listener_adapters.h): - PyClientListener — 15 hub events (connect, chat, users) - PyClientManagerListener — 7 events (NEW, not in BridgeListeners) - PySearchManagerListener — 1 event (search results) - PyQueueManagerListener — 18 events (all dcpp queue events) - PyDownloadManagerListener — 5 events (transfer progress) - PyUploadManagerListener — 6 events (transfer + waiting) - PyTimerManagerListener — 2 events (second + minute tick) All adapters pass Python-safe types (strings, ints, types.h structs) rather than raw dcpp pointers. SWIG directors + threads=1 handle GIL. EisPyContext gains 14 subscribe/unsubscribe methods: addHubListener/removeHubListener (per-hub ClientListener) addClientManagerListener/removeClientManagerListener (NEW) addSearchListener/removeSearchListener addQueueListener/removeQueueListener addDownloadListener/removeDownloadListener addUploadListener/removeUploadListener addTimerListener/removeTimerListener BridgeListeners (default convenience multiplexer) is unchanged. 89 new tests in test_swig_listeners.py — all pass.
Root cause 1 — DCContext SWIG heap corruption: SWIG's view of DCContext omitted the conditional #ifdef WITH_DHT and #ifdef LUA_SCRIPT members (two extra unique_ptrs = 16 bytes). Python-side 'new DCContext()' allocated an undersized object; the C++ constructor then zeroed past the allocation, silently corrupting heap metadata. Crash manifested later as 'corrupted size vs. prev_size' / SIGSEGV during unrelated allocs. Fix: %nodefaultctor on DCContext — Python can no longer construct it directly. All access goes through getContext() / EisPyContext. Root cause 2 — EisPyContext sequential init/shutdown: dcpp's DCContext::shutdown() + Util::uninitialize() leave the library in a non-restartable state. Calling dcpp::startup() a second time corrupted memory. Fix: make dcpp a permanent process-lifetime singleton. EisPyContext::shutdown() cleans only per-instance state (hubs, listeners, file lists). The DCContext is never destroyed — the OS reclaims resources at exit. Tests: 284 non-server tests pass (exit 0), no heap corruption.
SWIG wrappers: - dcpp_connection_manager.i: port accessors, connect/disconnect/shutdown - dcpp_dyndns.i: load/stop - dcpp_context.i: add getConnectionManager(), getDynDNS() accessors - eispy_context.i: connection_manager, dyndns properties - dc_client.py, async_client.py: connection_manager, dyndns properties - dc_core.i: include new .i files Tests: - conftest.py: session-scoped bcrypt.gensalt patch (rounds=4) — fixes apparent test hang caused by ~0.35s per hash × 91 tests = 91s runtime - test_swig_managers.py: ConnectionManager + DynDNS type/property tests 757 passed, 21 deselected in ~49s (was ~91s before bcrypt fix)
- Guard TestTLSEncryption with TEST_HUB_URL env var (skip when unset) instead of defaulting to a live hub that crashes on macOS CI - Fix async_client fixture: use object.__new__ to avoid creating an orphaned DCClient whose EisPyContext C++ destructor can run during GC while the real instance is active
…t async - Add DCClientProtocol (runtime_checkable Protocol) in protocol.py defining the unified async interface for both local and remote clients - Make all AsyncDCClient query/mutation methods async to match protocol - Add missing protocol methods: set_priority, reload_config, get_transfer_stats, get_hash_status, get_share_size, get_shared_files - Remove all _async backward-compat aliases from RemoteDCClient - Remove TestSyncMethodsRaiseTypeError (obsolete — methods are now coroutines) - Update all callers: tests, examples, README - Export DCClientProtocol from eiskaltdcpp.__init__ - Update README: architecture diagram, project structure, wheel platforms, RemoteDCClient examples, new DCClientProtocol section
- Add RUNTIME_OUTPUT_DIRECTORY so .pyd (DLL) goes to correct location on Windows (LIBRARY_OUTPUT_DIRECTORY only applies to .lib import libs) - Add per-config directory overrides (Release/Debug) to suppress multi-config generator subdirectories - Add protocol.py to POST_BUILD copy list (new file from API unification)
Add hub alias system so users can type 'eispy hub connect winter'
instead of 'eispy hub connect nmdcs://wintermute.sublevels.net:411'.
New module: python/eiskaltdcpp/hub_aliases.py
- load/save aliases from ~/.config/eispy/hubs.json
- EISPY_HUBS_FILE env var override, XDG_CONFIG_HOME support
- resolve() transparently passes through URLs (contains ://)
or looks up aliases with clear error messages
New CLI commands: eispy hub alias add/rm/ls
- Validates URL scheme on add (dchub://, nmdcs://, adc://, adcs://)
Alias resolution integrated into all hub-accepting CLI commands:
- hub connect/disconnect/users (positional args)
- chat send/pm/history (positional args)
- search query/results/clear --hub (options)
- queue add --hub (option)
- filelist request (positional arg)
- daemon/up --hub (multiple option)
All four DC URL schemes are supported: dchub://, nmdcs:// (TLS),
adc://, adcs:// (ADC+TLS).
37 new tests in tests/test_hub_aliases.py covering:
- Core module: load/save, add/remove, resolve, config paths
- CLI: alias add/rm/ls subcommands
- Resolution: connect, disconnect, chat, search, filelist with aliases
… listeners) This commit removes noexcept from all 320 listener on() methods and ConnectionManager allocation functions, allowing Speaker::fire()'s try/catch to catch std::bad_alloc instead of triggering std::terminate().
…pt removal) This commit removes all remaining noexcept from dcpp library functions, including BufferedSocket::write() which was the most likely source of the persistent std::bad_alloc -> std::terminate crash during file list serving.
The dcpp base Listener classes no longer have noexcept on their virtual on() methods. Keeping noexcept on the overrides in bridge_listeners.h and listener_adapters.h would cause std::terminate if any exception propagates from within the callback.
…FT test Limits $GetINFO requests to 10 per $NickList to prevent memory exhaustion from $MyINFO flood on large hubs.
…ostics Pin eiskaltdcpp to 72a39287 (file transfer tracing in QM::addList, CM::onSecond, ClientManager::connect, NmdcHub::connectToMe). Also add fprintf tracing to EisPyContext::requestFileList.
…ct exception tracing)
The bad_alloc in nmdcConnect was heap corruption, not memory exhaustion.
Capping user creation was wrong and didn't fix it. Instead:
- Bob now uses passive mode (INCOMING_CONNECTIONS=2). The flow becomes:
bob → hub: $RevConnectToMe bob alice
alice → hub: $ConnectToMe bob <alice_ip>:<port> (just sends a string, no alloc)
bob processes incoming $ConnectToMe on his clean heap
- Alice stays active with TCP listener on port 4200/4201
- Revert user creation caps in eiskaltdcpp (all getUser paths uncapped)
- NMDC_GETINFO_LIMIT now only limits $GetINFO requests (original purpose)
- Increase NMDC_GETINFO_LIMIT from 10 to 100 for the test
dcpp::Client conditionally inherits ClientScriptInstance when LUA_SCRIPT is defined, adding an 8-byte vtable pointer that shifts all member offsets. The bridge had PRIVATE visibility for the -DLUA_SCRIPT define, so the SWIG dc_core module compiled Client.h without it — ABI mismatch. Symptom: hubUrl.size() returned 0x55A4E7E34DF0 (a heap pointer) because the _M_string_length field was read at offset-8, landing on the _M_dataplus._M_p field instead. The string copy then attempted to allocate ~85 TB → std::bad_alloc.
The 'Singleton pattern prevents safe restart' blocker no longer applies: transfix/eiskaltdcpp removed the Singleton<> CRTP base entirely; a DCContext object owns all managers via dependency injection, so multiple independent contexts coexist and startup()/shutdown() cycles are bounded by context lifetime. Rewrite the TODO section accordingly.
Repin the FetchContent GIT_TAG from the old development commit c9940e1e to the v2.5.0 release tag (full SHA 3f01cb083a2b2d83c5944dd69d451df8401cc692) so the bindings build against the first tagged transfix/eiskaltdcpp release. Verified locally: a fresh isolated wheel build against this pin (USE_SYSTEM_EISKALTDCPP=OFF) succeeds and produces eiskaltdcpp_py-2.5.0.0.
Prefer a prebuilt, toolchain-matched core SDK over recompiling the core from source in every wheel, with a graceful fallback to the existing FetchContent source build when no SDK archive is present. - CMakeLists.txt: new EISKALTDCPP_SDK_DIR cache path, searched first (NO_DEFAULT_PATH) so a stale system copy cannot shadow the pinned version. Three tiers: SDK dir -> system package -> FetchContent. The imported target handles Windows import-lib + DLL, and the Lua-ABI autodetect is shared across the SDK and system paths. - .github/workflows/wheels.yml: EISKALTDCPP_CORE_TAG=v2.5.0; Linux and macOS CIBW_BEFORE_ALL best-effort download+extract the matching eiskaltdcpp-core-<tag>-<plat> archive to $HOME/.eiskaltdcpp-sdk/core and pass -DEISKALTDCPP_SDK_DIR to CMake. If the download fails, the source build path is used unchanged. Windows keeps the source build (already MSVC/vcpkg-matched).
…::Module Fixes the first end-to-end wheels.yml run (SDK-consume path): - CMakeLists.txt: request Python3 COMPONENTS ... Development.Module instead of the full Development component. Development pulls in Development.Embed, which needs a linkable libpython that manylinux images do not ship, so the Linux wheel config failed with "Could NOT find Python3 (missing: Python3_LIBRARIES Development)". - swig/CMakeLists.txt: link the SWIG extension against Python3::Module rather than Python3::Python (Embed). Extension modules must not link libpython on Unix (symbols resolve at load time); Windows still links pythonXX.lib via the Module target. - wheels.yml (macOS): cibuildwheel's bashlex evaluator for CIBW_ENVIRONMENT does not support pipelines inside $(...). Resolve the highest-versioned Homebrew GCC in CIBW_BEFORE_ALL_MACOS (real bash) and expose it via stable symlinks under $HOME/.gccwrap, referenced without a pipe in CMAKE_ARGS. - wheels.yml: add bcrypt to CIBW_TEST_REQUIRES (tests/conftest.py imports it at module level; Windows built the wheel with the SDK but failed at test on ModuleNotFoundError: No module named 'bcrypt').
The extension now builds and links against the prebuilt SDK on all platforms, but the repair step failed: - Linux: auditwheel "Cannot repair wheel, because required library libeiskaltdcpp.so.2.5 could not be located". - macOS: delocate "Could not find all dependencies". The SWIG extension records the core lib as a RUNPATH (Linux) / @rpath (macOS) dependency, but had no rpath pointing at the SDK lib directory, so the repair tools could not find the shared library to bundle it. - CMakeLists.txt: derive EISKALTDCPP_LIB_DIR from the resolved library. - swig/CMakeLists.txt: append EISKALTDCPP_LIB_DIR to the extension's BUILD_RPATH and INSTALL_RPATH (non-Windows) so auditwheel/delocate can locate and vendor libeiskaltdcpp.so.2.5 / libeiskaltdcpp.2.5.dylib. - wheels.yml: also export LD_LIBRARY_PATH to the SDK lib dir on Linux as belt-and-suspenders for auditwheel.
- Remove the macos-13 (x86_64) wheel matrix entry — GitHub has retired the Intel macOS runners. - Replace the ad-hoc CIBW_TEST_REQUIRES list with CIBW_TEST_EXTRAS "api,test,api-test" so the wheel's declared extras (fastapi, httpx, bcrypt, pydantic, pytest, ...) are installed for the test suite. Fixes ModuleNotFoundError for fastapi/httpx after the RPATH repair fix made the build+repair stages pass.
Build + SDK link + wheel-repair now all pass; only the test step remained:
- macOS: the prebuilt core dylib targets macOS 13.0
(CMAKE_OSX_DEPLOYMENT_TARGET=13.0), but the wheel was tagged macosx_11_0,
so delocate rejected the vendored dylib ("Library dependencies do not
satisfy target MacOS version 11.0"). Set MACOSX_DEPLOYMENT_TARGET=13.0 so
the wheel platform tag matches.
- Linux (900 passed, 24 failed): tests/ has no __init__.py and some tests do
`from tests.test_websocket import ...`. Run pytest via `cd {project} &&
python -m pytest tests/` so the project root lands on sys.path (implicit
namespace package), resolving the `No module named 'tests'` errors.
delocate failed: the bundled Homebrew gcc-16 libstdc++.6.dylib on the macos-latest runner (now macOS 26) requires macOS 26.0, so the wheel could not be tagged for an older OS. Pin the macOS runner to macos-14 — the same image the prebuilt core SDK arm64 is built on — so the Homebrew runtime deps target macOS 14, and set MACOSX_DEPLOYMENT_TARGET=14.0 inside CIBW_ENVIRONMENT_MACOS (the step-level env does not propagate into cibuildwheel's isolated build) so the wheel tag matches its vendored libs.
…ests
The `cd {project} && pytest` approach fixed Linux but failed on Windows:
cibuildwheel's temp cwd is on C: while the checkout is on D:, and `cd D:\...`
in cmd does not switch drives, so pytest reported "file or directory not
found: tests/".
Set pytest `pythonpath = ["."]` in pyproject.toml so the project root
(rootdir) is added to sys.path regardless of cwd, making
`from tests.test_websocket import ...` resolve everywhere. Revert
CIBW_TEST_COMMAND to invoke pytest with the absolute {project}/tests path.
The SWIG extension is a SHARED library, so on Windows its .pyd is a RUNTIME artifact (and the import .lib is ARCHIVE); the install(TARGETS) rule only declared LIBRARY DESTINATION, which matches neither on Windows. As a result _dc_core.pyd was silently omitted from the Windows wheel, producing "ImportError: cannot import name '_dc_core'" at test time (285 native tests skipped, 2 unguarded ones failed). Add RUNTIME DESTINATION alongside LIBRARY so the extension is packaged on Windows while Unix (LIBRARY) is unchanged.
…nix .so) Adding RUNTIME DESTINATION unconditionally to install(TARGETS dc_core) regressed BOTH Unix wheels: the .so vanished from the archive (auditwheel: "no ELF executable or shared library file found"; delocate: "Failed to find any binary with the required architecture"). The Unix .so is installed via the LIBRARY artifact (it is gitignored, so wheel.packages does not pick it up), and the extra RUNTIME kind somehow suppressed it. Split the rule per platform: Unix keeps the original LIBRARY-only install (known-good), Windows uses RUNTIME so _dc_core.pyd lands in the wheel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Full SWIG wrap: DCContext managers, EisPyContext, listener directors
Overview
Complete Python SWIG bindings for libeiskaltdcpp — 4 phases of modular .i file wrapping, class renaming, listener directors, and end-to-end integration testing against a live NMDC hub.
Phase 1: Modular SWIG manager wrapping (
1f28d58).ifiles wrapping all dcpp managers via DCContext typed accessorsdcpp_types.ifor CID, TTHValue, UserPtr, HintedUser, etc.dcpp_context.iwith typed manager properties (settings_manager,queue_manager, etc.)eispy_context.iwith%extendshortcuts for EisPyContexttest_swig_managers.pyPhase 2: SettingsManager + FavoriteManager enums (
af200af)Phase 3: Remove DCBridge → EisPyContext (
f54d288)DCBridge→EisPyContextacross all C++/SWIG/Python codebridge.h/bridge.cpp(replaced byeispy_context.h/.cpp)DCBridge = EisPyContextPython alias for backward compatibilityDCClient.pyto use direct manager callsPhase 4: Full listener directors (
2cf8343)listener_adapters.h:PyClientListener— 15 per-hub events (connect, chat, users)PyClientManagerListener— 7 global user/hub lifecycle events (NEW)PySearchManagerListener— search result eventsPyQueueManagerListener— all 18 queue eventsPyDownloadManagerListener— 5 download transfer eventsPyUploadManagerListener— 6 upload transfer eventsPyTimerManagerListener— second/minute tick eventson()to named virtual methodsthreads="1"handle GIL automaticallyBridgeListenersdefault multiplexer unchangedtest_swig_listeners.pyPhase 5: Integration testing & ABI fix
End-to-end integration tests against a live NMDC hub (
nmdcs://wintermute.sublevels.net:411), exercising the full stack from Python through SWIG directors into the C++ core and back.Integration test suite (
test_integration.py,test_file_transfer.py)EisPyContextinstances (alice + bob) connecting to a real hub simultaneouslyINCOMING_FIREWALL_PASSIVE) requests alice's (active) file list via$RevConnectToMe→$ConnectToMe→ TLS peer connectionDCClientCallbacksubclass with async event capture viaasyncio.EventABI mismatch fix:
LUA_SCRIPTpropagation (5bf4d28)dcpp::Clientconditionally inheritsClientScriptInstancewhenLUA_SCRIPTis defined, adding an 8-byte vtable pointer that shifts all member offsets. The bridge library'starget_compile_definitionsusedPRIVATEvisibility, so the SWIGdc_coremodule compiledClient.hwithout the define — ABI mismatch between the dcpp shared library and the SWIG wrapper.hubUrl.size()returned0x55A4E7E34DF0(~85 TB) because the_M_string_lengthfield was read at offset−8, landing on the_M_dataplus._M_p(heap pointer) field instead. The subsequent string copy attempted to allocate ~85 TB →std::bad_alloc.PRIVATE→PUBLICfortarget_compile_definitions(eiskaltdcpp_py_bridge ... LUA_SCRIPT)and bothtarget_include_directoriescalls in theif(EISKALTDCPP_HAS_LUA)block. CMake's transitive property system now propagates-DLUA_SCRIPTand the Lua/stub include paths to all consumers of the bridge library, including the SWIG module.Upstream eiskaltdcpp fixes (pinned at
c9940e1e)noexceptremoval — 414 incorrectnoexceptspecifiers removed across 61 dcpp files; exceptions now propagate instead of callingstd::terminateSpeaker::fire()exception safety — try/catch around listener dispatchpthread_createwith 1 MiB stacks on POSIX (down from 8 MiB default), reducing virtual memory from ~500 MB to ~100 MB on busy hubsConnectionManagerleak fix —nmdcConnect()/adcConnect()catch all exceptions fromconnect(), properly cleaning upUserConnectionobjectsnoexceptremoval in bridge — all listeneron()overrides inbridge_listeners.handlistener_adapters.hhadnoexceptremoved to match upstreamTest coverage
CI/CD pipeline
Usage example