Complete developer guide for building, testing, and continuing development.
- Repository structure
- Architecture overview
- Current state of completion
- Prerequisites
- Quickstart — building everything
- Layer-by-layer build guide
- Toolchain decisions and known constraints
- Test strategy
Automerge.Windows/
├── build.ps1 # Top-level build + test script
├── CMakeLists.txt # C++ wrapper + GoogleTest
├── rust-core/ # Layer 1 — Rust → C ABI (automerge_core.dll)
│ ├── Cargo.toml
│ ├── rust-toolchain.toml # Pins stable-x86_64-pc-windows-msvc
│ ├── include/automerge_core.h # Hand-maintained C header (ABI contract)
│ └── src/
├── cpp-wrapper/ # Layer 2 — C++ RAII wrapper (automerge_wrapper.lib)
│ ├── CMakeLists.txt
│ ├── include/automerge/
│ │ ├── Document.hpp
│ │ ├── SyncState.hpp
│ │ └── Error.hpp
│ └── src/
├── winrt-component/ # Layer 3 — WinRT projection (Automerge.Windows.dll)
│ ├── Automerge.Windows.idl # WinRT interface definitions
│ ├── Document.h / Document.cpp
│ ├── SyncState.h / SyncState.cpp
│ ├── Helpers.hpp # IBuffer ↔ vector/span helpers
│ ├── dll_exports.cpp # DllGetActivationFactory / DllCanUnloadNow
│ ├── pch.h # Precompiled header root
│ └── build-winrt.ps1 # Standalone WinRT build script
├── csharp-wrapper/ # Layer 4 — C# P/Invoke wrapper
│ ├── AutomergeWindows.csproj
│ ├── NativeMethods.cs
│ ├── Document.cs / SyncState.cs / Extensions.cs
└── tests/
├── cpp/ # GoogleTest suite (29 tests)
├── csharp/ # xUnit P/Invoke suite (29 tests)
└── winrt/ # xUnit WinRT projection suite (13 tests)
├── AutomergeWinRTTests.csproj
├── WinRTDocumentTests.cs
└── WinRTSyncTests.cs
┌─────────────────────────────────────────────┐
│ WinRT Test Suite (C# via CsWinRT) │ tests/winrt/ (13 tests)
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ WinRT Projection Layer │ Automerge.Windows.dll + .winmd
│ (C++/WinRT; IBuffer ↔ Automerge ops) │
└──────────────────┬──────────────────────────┘
│ (static link)
┌──────────────────▼──────────────────────────┐
│ C++ RAII Wrapper (automerge_wrapper.lib) │ cpp-wrapper/
│ automerge::Document / SyncState │
└──────────────────┬──────────────────────────┘
│ (import lib)
┌──────────────────▼──────────────────────────┐
│ C ABI — Rust (automerge_core.dll) │ rust-core/
│ AMcreate_doc, AMsave, AMmerge … │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ C# P/Invoke wrapper (AutomergeWindows.dll) │ csharp-wrapper/ (29 tests)
│ calls automerge_core.dll directly │
└──────────────────────────────────────────────┘
The C ABI (automerge_core.h) is the stable ABI contract. Everything above
it is a language-level projection over the same Rust implementation.
| Layer | Files | Status | Tests |
|---|---|---|---|
Rust C ABI (automerge_core.dll) |
rust-core/ |
✅ Complete | 24 passing |
| C++ RAII wrapper | cpp-wrapper/ |
✅ Complete | 29 passing |
| WinRT projection | winrt-component/ |
✅ Complete | 13 passing |
| C# P/Invoke wrapper | csharp-wrapper/ |
✅ Complete | 29 passing |
Total: 95 tests passing.
| Tool | Version | How to install |
|---|---|---|
| Git | any | — |
| Rust (via rustup) | stable | winget install Rustlang.Rustup then rustup show |
| CMake | 3.25+ | winget install Kitware.CMake |
| Ninja | 1.12+ | winget install Ninja-build.Ninja |
| .NET SDK | 9.0+ | winget install Microsoft.DotNet.SDK.9 |
| VS 2019 Build Tools | 14.29.x | Required — has complete MSVC (headers + libs) |
IMPORTANT: The WinRT component uses Windows SDK 10.0.22621.0 for compilation (to avoid a C1001 internal compiler error in MSVC 14.29 when compiling against the newer SDK's cppwinrt headers). Both SDK versions must be installed.
Download VS 2019 Build Tools and install the "C++ build tools" workload. This provides:
vcvarsall.batatC:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\- Full MSVC headers (
include\) and libraries (lib\x64\) cl.exe,link.exe
Note on VS 2022/2026: Community editions of VS 2022 and VS 2026 installed via the normal installer on this machine do NOT include the MSVC
include\directory (onlybin\andlib\are present). Only VS 2019 Build Tools has a complete installation.build-winrt.ps1always uses VS 2019 viavcvarsall.bat.
- 10.0.22621.0 — used for WinRT compilation (cppwinrt headers, um, ucrt, winrt)
- 10.0.26100.0 (or any newer) — used for
midl.exeandcppwinrt.exetools
Both SDKs are installed by default when you install VS Build Tools.
# From repo root:
.\build.ps1What the script does:
- Locate
vcvarsall.bat(VS 2019 Build Tools → VS 2022 → VS 2026) - Build
automerge_core.dllviacargo build --release - Run 24 Rust C-API tests
- Configure C++ wrapper + tests with CMake + Ninja
- Run 29 C++ GoogleTest tests
- Build C# P/Invoke wrapper; run 29 xUnit tests
- Build WinRT component (calls
winrt-component\build-winrt.ps1) - Run 13 WinRT xUnit tests
.\build.ps1 # Full build + test (release profile)
.\build.ps1 -Profile debug # Debug build
.\build.ps1 -SkipCpp # Skip CMake / C++ steps
.\build.ps1 -SkipCsharp # Skip dotnet / C# steps
.\build.ps1 -TestOnly # Just run tests (requires pre-built artifacts).\winrt-component\build-winrt.ps1 -Profile releaseOutput:
build-winrt\release\Automerge.Windows.dll— WinRT component DLLbuild-winrt\release\Automerge.Windows.lib— import librarybuild-winrt\release\Automerge.Windows.winmd— WinRT metadata
cd rust-core
cargo build --release
cargo testOutput: target/release/automerge_core.dll + target/release/automerge_core.dll.lib
The C header rust-core/include/automerge_core.h is hand-maintained (no
cbindgen) so the ABI contract is stable and version-controlled independently.
The toolchain is pinned in rust-core/rust-toolchain.toml:
[toolchain]
channel = "stable-x86_64-pc-windows-msvc"# First: set up the MSVC environment
& "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64
# Then from repo root:
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DAUTOMERGE_BUILD_TESTS=ON
cmake --build build
cd build && ctest --output-on-failureOutput: build\automerge_wrapper.lib
.\winrt-component\build-winrt.ps1 -Profile releaseThe script runs these steps:
- MIDL →
Automerge.Windows.winmd(fromAutomerge.Windows.idl) - cppwinrt → generated C++/WinRT projection headers +
module.g.cpp - cl.exe → compile
Document.cpp,SyncState.cpp,dll_exports.cpp,module.g.cpp - link.exe →
Automerge.Windows.dll(linkingautomerge_wrapper.lib+automerge_core.dll.lib) - Copy
.winmdto output
Known toolchain constraint: MSVC 14.29 (VS 2019) has a C1001 internal compiler
error when compiling Windows SDK 10.0.26100.0 cppwinrt headers (winrt/base.h).
build-winrt.ps1 works around this by:
- Calling
vcvarsall.bat x64(which sets up INCLUDE/LIB for the latest SDK) - Replacing the SDK version in
INCLUDEandLIBfrom10.0.26100.0to10.0.22621.0
This ensures the compiler uses the older (compatible) cppwinrt headers.
cd tests/csharp
dotnet test -p:Platform=x64 -p:RuntimeIdentifier=win-x64cd tests/winrt
dotnet test -p:Platform=x64 -p:RuntimeIdentifier=win-x64This project targets x64 (AMD64) Windows with the MSVC toolchain.
rust-toolchain.tomlpinsstable-x86_64-pc-windows-msvc- C++ is compiled with
cl.exefrom VS 2019 Build Tools - MSVC import libraries use
.dll.libextension (not MinGW's.dll.a)
Windows SDK 10.0.26100.0's winrt/base.h uses C++20 template features that
trigger fatal error C1001: Internal compiler error (ICE) in MSVC 14.29 at
winrt/base.h(5069). The older SDK 10.0.22621.0 does not have this problem.
Workaround in build-winrt.ps1:
# After vcvarsall.bat sets INCLUDE/LIB to 10.0.26100.0:
$env:INCLUDE = $env:INCLUDE -replace "10\.0\.26100\.0", "10.0.22621.0"
$env:LIB = $env:LIB -replace "10\.0\.26100\.0", "10.0.22621.0"If VS 2022/2026 is ever installed with complete MSVC headers (include\ directory),
update build-winrt.ps1 to prefer that compiler with the latest SDK.
Inside namespace winrt::Automerge::Windows::implementation, the name Windows
resolves to winrt::Automerge::Windows (the parent namespace), NOT winrt::Windows.
All references to Windows::Storage::Streams::IBuffer therefore require the full
winrt::Windows::Storage::Streams::IBuffer prefix in headers and .cpp files.
The WinRT component uses C++/WinRT 2.0 which generates WINRT_GetActivationFactory
and WINRT_CanUnloadNow (with C linkage, from extern "C" in winrt/base.h).
dll_exports.cpp provides the standard COM names DllGetActivationFactory and
DllCanUnloadNow as forwarding wrappers.
module.g.cpp (generated by cppwinrt) includes "Document.h" and "SyncState.h".
Because it lives in the generated/ subdirectory, the compiler searches generated/
first for those includes — which would find the generated stubs with
static_assert(false, ...). build-winrt.ps1 deletes the generated stubs after
running cppwinrt so the compiler falls back to the INCLUDE environment, which has
the winrt-component/ source directory first.
Tests the raw C ABI directly in Rust using extern "C" declarations. Covers:
create/destroy, save/load, heads, changes (full + incremental), apply, merge,
sync (one-way + bidirectional), error handling.
GoogleTest suite against automerge_wrapper.lib. Tests RAII lifecycle, move
semantics, persistence, heads, changes, merge, values, sync.
xUnit suite via AutomergeWindows.dll (P/Invoke). Covers the same operations
plus IDisposable safety, double-dispose.
xUnit suite using CsWinRT-generated projections of Automerge.Windows.winmd.
Tests the WinRT activation factory, Document and SyncState runtime classes
via the WinRT ABI (IBuffer, hstring).
CsWinRT generates C# projection code at build time from the .winmd. The
dotnet test command copies Automerge.Windows.dll and automerge_core.dll
to the test output directory so the WinRT activation factory can load them
at runtime without COM registration.