Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b15d63e
doc: Bump version 11 > 12
ryanofsky Apr 15, 2026
36c91a0
util, refactor: Add ProcessId type alias and use it
ryanofsky Apr 30, 2025
94af41b
util, refactor: Add SocketId type alias and use it
ryanofsky Apr 30, 2025
beaa50a
util, refactor: Add ConnectInfo type alias and use it
ryanofsky Apr 30, 2025
b16f8c4
util, refactor: Handle forking inside ExecProcess
ryanofsky Apr 17, 2026
022b29b
util, refactor: Add SocketPair() and use it in SpawnProcess
ryanofsky Apr 30, 2025
24c5e57
util: Clear FD_CLOEXEC on child socket before exec
ryanofsky Apr 30, 2025
3c81cf2
proxy, refactor: Replace EventLoop wakeup fd integers with KJ stream …
ryanofsky Apr 30, 2025
17a1952
cmake: Bump minimum required Cap'n Proto version to 0.9
ryanofsky Apr 16, 2026
091f5e1
proxy, refactor: Change ConnectStream and ServeStream to accept strea…
ryanofsky Apr 30, 2025
bfc2db7
proxy: Call shutdownWrite() in Connection destructor
ryanofsky Apr 30, 2025
1060a95
util, refactor: Fix PtrOrValue constructor for move-only types on MSVC
ryanofsky Apr 17, 2026
362d416
proxy, refactor: Fix C4305 truncation warning in Accessor on MSVC
ryanofsky Apr 22, 2026
3fd227c
type-interface, refactor: Fix typename decltype() SFINAE in CustomBui…
ryanofsky Apr 22, 2026
926ae35
ci: Check out bitcoin/bitcoin PR #35084 instead of master
ryanofsky Apr 16, 2026
28e4c7f
proxy: Fix shutdownWrite() exception handling on macOS with dynamic l…
ryanofsky Apr 17, 2026
f6aa627
ipc: Wrap mpgen main() in try-catch to print errors
ryanofsky Apr 20, 2026
7f513a4
doc: Remove trailing whitespace
ryanofsky Apr 17, 2026
c9aa806
cmake: Replace capnp_PREFIX path construction with cmake-provided sym…
ryanofsky Apr 21, 2026
7cb83a5
cmake: Fix CapnProto tool paths broken by Ubuntu Noble packaging bug
ryanofsky Apr 22, 2026
4f58c8c
util: Add Windows support
ryanofsky Apr 30, 2025
7fd5ec4
util: drop POSIX/pthread dependencies to enable MSVC builds
ryanofsky Apr 17, 2026
d97c586
ci: Add Windows cross-build and native test jobs
Sjors Apr 20, 2026
07a7f71
util, gen: undef Windows COM macros and rename interface identifier
Sjors Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/bitcoin-core-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ concurrency:

env:
BITCOIN_REPO: bitcoin/bitcoin
# Temporary: use PR #35084 until it merges; revert to refs/heads/master after
BITCOIN_CORE_REF: refs/pull/35084/merge
LLVM_VERSION: 22
LIBCXX_DIR: /tmp/libcxx-build/

Expand Down Expand Up @@ -79,6 +81,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: ${{ env.BITCOIN_REPO }}
ref: ${{ env.BITCOIN_CORE_REF }}
fetch-depth: 1

- name: Checkout libmultiprocess
Expand Down Expand Up @@ -195,6 +198,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: ${{ env.BITCOIN_REPO }}
ref: ${{ env.BITCOIN_CORE_REF }}
fetch-depth: 1

- name: Checkout libmultiprocess
Expand Down
36 changes: 35 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ jobs:
export PATH="$(brew --prefix llvm)/bin:$PATH"
CI_CONFIG="ci/configs/macos.bash" bash ci/scripts/ci.sh

test-windows-cross:
runs-on: windows-latest
name: test • windows cross
needs: build
# GitHub-hosted max, nix build is very slow without cache
timeout-minutes: 360

steps:
- uses: actions/checkout@v6

- name: Download Windows artifacts
uses: actions/download-artifact@v8
with:
name: windows-cross-ucrt-build
path: windows-cross-artifact

- name: Run Windows tests
shell: pwsh
run: ci/scripts/windows_native_test.ps1 -ArtifactRoot windows-cross-artifact
build:
runs-on: ubuntu-latest

Expand All @@ -142,7 +161,7 @@ jobs:
strategy:
fail-fast: false
matrix:
config: [default, llvm, gnu32, sanitize, olddeps]
config: [default, llvm, gnu32, sanitize, olddeps, windows]

name: build • ${{ matrix.config }}

Expand Down Expand Up @@ -181,3 +200,18 @@ jobs:
env:
CI_CONFIG: ci/configs/${{ matrix.config }}.bash
run: ci/scripts/run.sh

- name: Package Windows artifacts
if: matrix.config == 'windows'
env:
CI_CONFIG: ci/configs/${{ matrix.config }}.bash
run: |
source "$CI_CONFIG"
nix develop --ignore-environment --keep CI_CONFIG "${NIX_ARGS[@]+"${NIX_ARGS[@]}"}" -f shell.nix --command bash ci/scripts/windows_package.sh

- name: Upload Windows artifacts
if: matrix.config == 'windows'
uses: actions/upload-artifact@v7
with:
name: windows-cross-ucrt-build
path: windows-cross-artifact
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ endif()
include("cmake/compat_find.cmake")

find_package(Threads REQUIRED)
find_package(CapnProto 0.7 QUIET NO_MODULE)
find_package(CapnProto 0.9 QUIET NO_MODULE)
if(NOT CapnProto_FOUND)
message(FATAL_ERROR
"Cap'n Proto is required but was not found.\n"
Expand Down Expand Up @@ -203,6 +203,10 @@ target_link_libraries(mpgen PRIVATE CapnProto::capnp-rpc)
target_link_libraries(mpgen PRIVATE CapnProto::capnpc)
target_link_libraries(mpgen PRIVATE CapnProto::kj)
target_link_libraries(mpgen PRIVATE Threads::Threads)
target_compile_definitions(mpgen PRIVATE
"CAPNP_EXECUTABLE=\"$<TARGET_FILE:CapnProto::capnp_tool>\""
"CAPNPC_CXX_EXECUTABLE=\"$<TARGET_FILE:CapnProto::capnpc_cpp>\""
"CAPNP_INCLUDE_DIRS=\"${CAPNP_INCLUDE_DIRS}\"")
set_target_properties(mpgen PROPERTIES
INSTALL_RPATH_USE_LINK_PATH TRUE)
set_target_properties(mpgen PROPERTIES
Expand Down
2 changes: 1 addition & 1 deletion ci/configs/olddeps.bash
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CI_DESC="CI job using old Cap'n Proto and cmake versions"
CI_DIR=build-olddeps
export CXXFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-unused-parameter -Wno-error=array-bounds"
NIX_ARGS=(--argstr capnprotoVersion "0.7.1" --argstr cmakeVersion "3.12.4")
NIX_ARGS=(--argstr capnprotoVersion "0.9.2" --argstr cmakeVersion "3.12.4")
BUILD_ARGS=(-k)
57 changes: 57 additions & 0 deletions ci/configs/windows.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
CI_DESC="CI job cross-compiling to Windows (MinGW UCRT) and testing with Wine"
CI_DIR=build-windows
# Cache the cross-toolchain closure to avoid rebuilding mingw + wine every run.
CI_CACHE_NIX_STORE=true

# Wine needs a writable prefix and XDG_RUNTIME_DIR to talk to its services.
# Pre-create the prefix outside of the nix shell so we can plumb it through
# (the shell uses --ignore-environment).
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-libmultiprocess}"
export WINEARCH="${WINEARCH:-win64}"
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
mkdir -p "$WINEPREFIX"
# Silence wine's verbose warnings about missing Windows features during tests.
export WINEDEBUG=-all

NIX_ARGS=(
--arg minimal true
--arg enableWine true
# Pin capnproto v1.4.0: v1.3.0 includes the upstream fix that moves
# cidr.c++ into kj-async (capnproto@a2deb05) so we no longer need a local
# patch for that, and v1.4.0 is the current stable release.
--arg capnprotoVersion '"1.4.0"'
--arg crossPkgs 'import <nixpkgs> { crossSystem = { config = "x86_64-w64-mingw32"; libc = "ucrt"; }; }'
# Wine stores its prefix under $HOME; preserve HOME so wineboot can initialize.
--keep HOME
--keep WINEPREFIX
--keep WINEARCH
--keep WINEDEBUG
--keep XDG_RUNTIME_DIR
)
export CXXFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-unused-parameter -Wa,-mbig-obj"

# When sourced from inside the nix shell (during ci.sh), initialize wine and
# pick up native capnp tools to use for build-time code generation. These
# steps are no-ops when sourced from outside the shell (e.g. by run.sh).
if command -v wineboot >/dev/null 2>&1; then
wineboot --init >/dev/null 2>&1 || true
fi
CAPNP_NATIVE=$(command -v capnp 2>/dev/null || true)
CAPNPC_CXX_NATIVE=$(command -v capnpc-c++ 2>/dev/null || true)

CMAKE_ARGS=(
-G Ninja
# Tell CMake we're targeting Windows so FindThreads picks Win32 threads
# and other platform checks behave correctly.
-DCMAKE_SYSTEM_NAME=Windows
-DCMAKE_SYSTEM_PROCESSOR=x86_64
# Run target-arch executables (mpgen, capnpc, mptest) through wine64.
-DCMAKE_CROSSCOMPILING_EMULATOR=wine64
# Avoid pulling in libgcc_s_seh-1.dll and libstdc++-6.dll at runtime.
-DCMAKE_EXE_LINKER_FLAGS="-static-libgcc -static-libstdc++"
# Use native capnp/capnpc-c++ for build-time code generation so cmake
# doesn't try to exec target-arch .exe binaries directly.
${CAPNP_NATIVE:+-DCAPNP_EXECUTABLE=$CAPNP_NATIVE}
${CAPNPC_CXX_NATIVE:+-DCAPNPC_CXX_EXECUTABLE=$CAPNPC_CXX_NATIVE}
)
BUILD_ARGS=(-k 0)
36 changes: 36 additions & 0 deletions ci/patches/capnproto-wine-invalid-function.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
From: libmultiprocess CI <ci@invalid>
Subject: [PATCH] kj: Treat ERROR_INVALID_FUNCTION from
GetFileInformationByHandleEx as non-fatal

Wine's NtQueryInformationFile returns STATUS_NOT_IMPLEMENTED for any
FILE_INFORMATION_CLASS it does not handle (see the default case in
dlls/ntdll/unix/file.c:NtQueryInformationFile in the wine source).
RtlNtStatusToDosError maps STATUS_NOT_IMPLEMENTED (0xC0000002) to
ERROR_INVALID_FUNCTION (1), so GetFileInformationByHandleEx returns 1
when called with FileCompressionInfo (and other classes Wine doesn't
implement) instead of the ERROR_CALL_NOT_IMPLEMENTED (120) that the
existing fallback expects. Without this, cap'n proto's KJ filesystem
layer throws an uncaught exception when running cross-compiled capnp.exe
under Wine. Treat ERROR_INVALID_FUNCTION the same way: skip the sparse
file space-usage query and continue.

Forwarded: https://github.com/capnproto/capnproto/pull/2633
Drop once that PR is merged and a release containing it is pinned.
---
c++/src/kj/filesystem-disk-win32.c++ | 5 +++++
1 file changed, 5 insertions(+)

--- a/c++/src/kj/filesystem-disk-win32.c++
+++ b/c++/src/kj/filesystem-disk-win32.c++
@@ -380,6 +380,11 @@
case ERROR_CALL_NOT_IMPLEMENTED:
// Probably WINE.
break;
+ case ERROR_INVALID_FUNCTION:
+ // Also WINE: NtQueryInformationFile returns STATUS_NOT_IMPLEMENTED for
+ // unsupported FileInfoClass values, which RtlNtStatusToDosError maps to
+ // ERROR_INVALID_FUNCTION (not ERROR_CALL_NOT_IMPLEMENTED).
+ break;
case ERROR_INVALID_PARAMETER:
// Probably VeraCrypt. See https://github.com/capnproto/capnproto/issues/2176
break;
39 changes: 39 additions & 0 deletions ci/scripts/windows_native_test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
param(
[Parameter(Mandatory = $true)]
[string]$ArtifactRoot
)

# Copyright (c) The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.

$ErrorActionPreference = "Stop"
# Prevent PowerShell 7 from turning native-command stderr into a terminating
# error before we get a chance to see it.
$PSNativeCommandUseErrorActionPreference = $false

$artifactPath = (Resolve-Path $ArtifactRoot).Path
$env:PATH = "$artifactPath;$env:PATH"

$mptest = Join-Path $artifactPath "test\mptest.exe"
& $mptest 2>&1 | ForEach-Object { "$_" }
$code = $LASTEXITCODE
Write-Host ("mptest exit code: {0} (0x{0:X8})" -f $code)
if ($code -ne 0) {
exit $code
}

$exampleDir = Join-Path $artifactPath "example"
Push-Location $exampleDir
try {
$output = "2+2`nexit`n" | & ".\mpexample.exe" 2>&1 | Out-String
Write-Host $output
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
if ($output -notmatch "mpprinter:" -or $output -notmatch "Bye!") {
throw "Unexpected mpexample output."
}
} finally {
Pop-Location
}
97 changes: 97 additions & 0 deletions ci/scripts/windows_package.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
#
# Copyright (c) The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.
#
# Package cross-compiled Windows binaries + runtime DLLs into a directory
# suitable for uploading as a workflow artifact. Must be run inside the
# nix shell produced by `ci/configs/windows.bash` so the mingw toolchain
# (objdump, g++) is on PATH.

export LC_ALL=C.UTF-8

set -o errexit -o nounset -o pipefail -o xtrace

readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly REPO_DIR="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"

[ "${CI_CONFIG+x}" ] && source "$CI_CONFIG"
: "${CI_DIR:=build-windows}"

readonly BUILD_DIR="${REPO_DIR}/${CI_DIR}"
readonly ARTIFACT_DIR="${REPO_DIR}/windows-cross-artifact"

# Nix's cross stdenv exports these; fall back to the canonical names if not.
: "${CXX:=x86_64-w64-mingw32-g++}"
: "${OBJDUMP:=x86_64-w64-mingw32-objdump}"

copy_artifact_file() {
install -D -m 0755 "$1" "${ARTIFACT_DIR}/$2"
}

# Runtime DLLs for cross-compiled dependencies live in the sibling bin/
# directory of each -L path the cross stdenv adds to NIX_LDFLAGS (e.g.
# capnproto, mcfgthreads). Collect those bin/ dirs once for dll lookup.
EXTRA_DLL_DIRS=()
for flag in ${NIX_LDFLAGS:-}; do
case "${flag}" in
-L*)
candidate="${flag#-L}/../bin"
[[ -d "${candidate}" ]] && EXTRA_DLL_DIRS+=("${candidate}")
;;
esac
done

copy_runtime_dlls() {
local exe="$1"
local dll dll_path dir
while read -r dll; do
[[ -n "${dll}" ]] || continue
# Skip DLLs that ship with Windows / Wine.
case "${dll}" in
ADVAPI32.dll|COMBASE.dll|COMCTL32.dll|GDI32.dll|KERNEL32.dll|OLE32.dll|OLEAUT32.dll|RPCRT4.dll|SHELL32.dll|UCRTBASE.dll|USER32.dll|WS2_32.dll)
continue
;;
esac
dll_path="$(${CXX} -print-file-name="${dll}")"
if [[ "${dll_path}" == "${dll}" || ! -f "${dll_path}" ]]; then
dll_path=""
for dir in "${EXTRA_DLL_DIRS[@]+"${EXTRA_DLL_DIRS[@]}"}"; do
if [[ -f "${dir}/${dll}" ]]; then
dll_path="${dir}/${dll}"
break
fi
done
fi
if [[ -z "${dll_path}" || ! -f "${dll_path}" ]]; then
case "${dll}" in
lib*.dll)
echo "Could not locate runtime DLL ${dll}." >&2
exit 1
;;
*)
continue
;;
esac
fi
install -D -m 0755 "${dll_path}" "${ARTIFACT_DIR}/${dll}"
done < <(${OBJDUMP} -p "${exe}" | awk '/DLL Name: / {print $3}' | sort -u)
}

rm -rf "${ARTIFACT_DIR}"

readonly EXES=(
test/mptest.exe
example/mpexample.exe
example/mpcalculator.exe
example/mpprinter.exe
)

for exe in "${EXES[@]}"; do
copy_artifact_file "${BUILD_DIR}/${exe}" "${exe}"
done

for exe in "${EXES[@]}"; do
copy_runtime_dlls "${BUILD_DIR}/${exe}"
done
Loading
Loading