Repository navigation
Add Windows support for the engine pipeline #16
Description
Activity
- addedbuildBuild, packaging and developer setupBuild, packaging and developer setupengineResolution / call-graph engine rulesResolution / call-graph engine rulesenhancementNew feature or requestNew feature or requestplatformOS / toolchain portabilityOS / toolchain portabilitywindowsMicrosoft Windows supportMicrosoft Windows support
on Aug 30, 2026 swapnilpaliwal-sd commented
on Aug 30, 2026 ContributorAuthorMore actionsThis needs to be true as the support for languages increases.
swapnilpaliwal-sd commented
on Sep 7, 2026 ContributorAuthorMore actionsRe-surveyed this against
mainafter #245 merged. The framing here holds up — soufflé is not the
blocker, our pipeline is — but three things have changed and one blocker is missing from the list.Blocker 1 shrank, and its line refs are stale
Header discovery moved out of
run-souffle.shintosrc/pipeline/souffle-include.shin #245, and
the Cygwin branch is nowrun-souffle.sh:282, not:268.More usefully, the vcpkg case is now nearly free. #245 replaced the
[ -d "$p/include/souffle" ]
test with a probe for the header the compiler actually opens, because Homebrew installs the headers
twice and a directory test cannot tell the layouts apart. vcpkg is the single-copy layout —
installed/x64-windows/include/souffle/*.h— which is exactly the layout the old test got wrong.
Verified locally on soufflé 2.5 that on a single-copy tree-I <prefix>/include/soufflefails with
fatal error: 'souffle/CompiledSouffle.h' file not foundwhile-I <prefix>/includecompiles, and
thatsouffle_include_underpicks the latter. So this is no longer "add probing logic", it is one
entry in the fallback list:"${VCPKG_ROOT:+$VCPKG_ROOT/installed/${VCPKG_DEFAULT_TRIPLET:-x64-windows}}"Worth noting that before #245 this issue was unachievable on any single-copy layout, Windows
included — the engine could not be compiled at all. #245 is a prerequisite for this issue, not
merely adjacent to it.The missing blocker: the compile invocation itself
run-souffle.sh:283is a GCC/Clang driver line, and it is the largest piece of work here:c++ -std=c++17 -O3 -march=native -w $CXX_PLATFORM -I "$INNER" "$INT/souffle-program.cpp" -o "$BIN.tmp.$$"
On a native MSVC toolchain every token differs —
cl.exe,/std:c++17,/O2,/I,/Fe:,/w
— and-march=nativehas no MSVC equivalent at all (nearest is an explicit/arch:AVX2, which is a
different decision, not a translation).-Wa,-mbig-objis a GNU assembler flag, so the existing
Cygwin branch only means anything under Cygwin's gcc; the MSVC form of that same 32768-section cap
is/bigobj, which is what soufflé's own CI passes (-DCMAKE_CXX_FLAGS=/bigobj), and its Windows
job carries the note "Visual Studio must be in the environment because cl.exe is required for
compiled Souffle" — it runs the synthesised suite, not just the interpreter, sosouffle -g→
compile → run is genuinely supported upstream.This means proposed scope (a) needs rewording. "Extend the existing
uname -scase" undersells it:
you cannot extend a flag into a different compiler driver. What's needed is a small abstraction —
resolve a compiler and emit its flag vector — with two implementations. Still consistent with
preferring (a) over a separate.ps1, and I'd still prefer (a); it is just not a one-line case arm.Two smaller items in the same area:
- Path translation. Under Git Bash / MSYS2,
-I "$INNER"and-o "$BIN.tmp.$$"handcl.exe
MSYS-style paths (/c/vcpkg/...) that it cannot open. Needscygpath -wat the boundary. uname -smatching. Git Bash reportsMINGW64_NT-*and MSYS2MSYS_NT-*. The existing
CYGWIN*arm matches neither, so today the section-cap workaround does not apply on the two
shells people are most likely to use.
The note about spaced paths is no longer true
The closing note says the suite passes from a path containing spaces. That was true when written;
the gate added by #245 is not.test/tools/souffle-include-test.sh:26word-splits the resolver it
bootstraps from, so with soufflé under a spaced prefix it printsSKIPand exits 0 — green, having
asserted nothing. Filed as #254 with the reproduction. It is a one-line quoting fix, but it matters
disproportionately here:C:\Program Files\...andC:\Users\First Last\...arrive as
/c/Program Files/..., so on Windows that gate would be permanently self-disabled while looking
green, on the platform whose include layout is least exercised. Worth taking before this issue, not
after.Two additions to prerequisites and acceptance
No soufflé Windows binary exists. The 2.5 release ships only
.deband.rpm(checked the
release assets). So the Windows prerequisite is build-from-source via vcpkg + chocolatey, matching
what upstream CI does — a heavier ask than the README acceptance line implies, and worth stating
explicitly since it is the first thing a contributor hits.The Parser is an unlisted prerequisite. Acceptance says
test/python/run-tests.shpasses on
Windows, but the harness needsAXIOM_PARSER→Parser/dist/index.js, and the Parser depends on
tree-sitter,tree-sitter-java,tree-sitter-pythonandtree-sitter-groovy— node-gyp native
modules that need VS Build Tools on Windows, with noosfield in itspackage.json. No blocking
logic found on that side (noprocess.platformbranching in parser source), but the native build is
a real gate and the acceptance criterion cannot be met without it. It may deserve its own issue on
the Parser repo.Shell layer is clear
Swept all 24 tracked
.shfiles for constructs that break under a Windows POSIX shell. Nothing
found: 12 process substitutions, 113/dev/null, plus/dev/stderr,/dev/zero,readlink -f,
mktemp -dandshasumare all fine under Git Bash / MSYS2 / Cygwin.run-souffle.sh:282is the
onlyunamebranch in the tree. So the bash-ness of the pipeline is not the problem it looks like —
under WSL2 it should work today, post-#245, with no code change. That split is worth making explicit
in the scope: WSL2 is verification and documentation; native MSVC is the compiler abstraction
above. They are very different amounts of work and only the second needs any of it.- Path translation. Under Git Bash / MSYS2,
Ran the TypeScript front end end to end on Git Bash / MSYS2 (
MINGW64_NT-10.0,uname -r3.6.5) against a statically-linked Soufflé 2.x Windows build with MinGW-w64 as thesouffle -gcompiler. It works, and the 28-case suite is fully green on both passes with--oracle. Getting there needed five changes; three of them are not in this issue's list, and the two that are behave slightly differently from the description. Everything below is at engine33b2958, parser2de08cf(rebuilt,dist/index.jsnewer thanHEAD).Confirmed as described
uname -smatching — exactly right, and it is the first thing that bites. Git Bash reportsMINGW64_NT-*, so theCYGWIN*arm matches nothing. Worth noting the arm turns out not to be needed on MinGW: the generated translation unit for the TypeScript rule set compiles without-mbig-obj(8.1 MB binary, 24 rule files / 482 distinct relations). So the section cap is not currently hit here — but the arm still needs to match for the other cases it guards.Symlink privileges — real, and the failure is worse than "needs Developer Mode". Without
SeCreateSymbolicLinkPrivilege, MSYSln -sdoes not fail: it silently deep-copies the directory. So a copy fallback keyed onln -sreturning non-zero would never fire. Two of the TypeScript preflights (self-staging-test.sh,mirror-selflink-test.sh) build workspace self-link shapes withln -sand then assert a predicate about them, so they fail on a shape the test never actually created — a false red aboutis_project_itself, which is correct. A Windows directory junction needs no privilege and MSYS reports it as a symlink with a correctrealpath, which is what I used.Not in the list
1. The generated
#includepaths, which is a different boundary from-Iand-o.The comment above has
-I "$INNER"and-o "$BIN.tmp.$$"covered. ButSRCis also interpolated into the generated program text:SRC="$(cd "$(dirname "$0")/.." && pwd)" # -> /c/… echo "#include \"$DL/decls_base.dl\"" # -> #include "/c/…/decls_base.dl"
and Soufflé's own preprocessor is a native
gcc.exe, which reads/c/…as<current drive>\c\…:Pre-processor command failed with code 1: 'gcc.exe -x c -E -I "." -DRAM_DOMAIN_SIZE=64 "…/souffle-program.dl" 2> nul' Uncaught exception: Failed to read inputSide effect worth knowing because it is confusing to debug: this leaves a stray
C:\c\<…>directory tree on disk, created by whichever native tool got the path next.cygpath -m "$SRC"right after the assignment fixes every derived path at once ($DL,$ENG,$ENG2,$TPL), and the mixed form is accepted by both MSYS bash and the native tools, so no other line needs to change.2. The cached engine binary has no executable suffix, and the cache-hit test disagrees with the exec.
BIN="$CACHE_DIR/souffle-engine-$NEW" if [ ! -x "$BIN" ]; then … fi "$BIN" -F "$FACTS" -D "$OUT"
MSYS
execveresolves a bare name by appending.exe, so a PE stored under an extension-less name isENOENTand bash reportsrc=127. Meanwhile[ -x "$BIN" ]is true, becausestat()does append.exe. So on a warm cache the script prints▶ reusing cached binaryand then cannot run it. Verified directly: the same bytes exit 0 when the file is named*.exeand 127 when it is not, on the samePATH.An
EXE=".exe"under the sameuname -scase arm covers it; note-o "$BIN.tmp.$$"does not need changing, because MinGWgcconly appends.exewhen the output name has no extension and.tmp.<pid>counts as one.3. A transient file lock fails a solve that already finished.
rm -rf "$FACTS" "$INT/souffle-program.cpp"
runs after every output relation is written. On Windows a just-written multi-megabyte
.factsfile is still briefly held, so:rm: cannot remove '…/lib_ts_type_reference.facts': Device or resource busyand
set -eturns a complete solve intosolve failed. Measured: 9,748 rows already written toout/call-chain-edges.csv, then exit 1. Retrying the identicalrma few seconds later succeeds every time. Since this is per-run scratch deleted after all measurement is on disk, it should not be able to fail the run at all — a retry loop plus|| trueis enough, and that is true on every platform.One correction to the Notes
Nothing in the test suites is a blocker
Not true for
test/typescript/, in three separate places, all in the ground-truth stack rather than the pipeline. I have filed them separately rather than expanding this issue, since they are test-harness mechanisms and not pipeline portability:- the call-site join compares a raw relative-path string while target identity goes through
realpath, so nothing joins and the dispatch-envelope block reports0 / precision 0.000instead of refusing; .source-rootis written by the shell and resolved by the Python readers, so every staged declaration lands under a nonexistent root and the client's own files are reported asTARGET NOT STAGED;tools/signature-impl-test.shasserts POSIX path syntax about a platform-native emitter, so it is a false red that aborts the whole suite before any case runs, while the mechanism it guards is working.
Two smaller ones, not filed:
run-evaluation.shneedsrsync, which ships with neither Git for Windows nor MSYS2. Only one invocation shape is used (-aplus--exclude), andtarhas the same--excludesemantics for those patterns, so a fallback is cheap if that is wanted.core.autocrlfistruein Git for Windows' system config, so a default clone materialises every committed LF golden as CRLF andcheck_golden'sdiff -qthen fails on line endings alone, for every case, in all three languages. There is no.gitattributesin the repo. I worked around it withgit -c core.autocrlf=false worktree add, but a one-line.gitattributeswould mean no Windows contributor ever sees it.
Acceptance criteria, as they stand today
Against this issue's list, with the five changes above applied locally (nothing committed):
- the TypeScript suite passes: 28/28 cases, both passes,
--oracle, every committed golden byte-for-byte - library staging works without symlink privileges — via junctions rather than a copy fallback
-
souffle -g→ compile → run works end to end under MinGW - headers discovered without
AXIOM_SOUFFLE_INCLUDE— not tested; I set the variable -
test/python/run-tests.sh— not run
Happy to hand over the exact diff for the three pipeline items if that is useful; it is 12 added lines in
src/pipeline/run-souffle.shand touches no rule, golden or template.- the call-site join compares a raw relative-path string while target identity goes through
- added a commit that references this issue
on Sep 14, 2026 Fresh Windows 11 dev setup via
npm link: two gaps not listed aboveVerified at
f60e4d4fon Windows 11, Node 24.11.1, npm 11.6.2, Git for Windows (default install), with no Soufflé, no C++ compiler and no WSL.What works:
npm install(including the prepare build of parser and dist; the unpublished@axiomcode/engine-*optionals are skipped without error),npm link,axiomcode --help, and parse + staging from any directory in Git Bash.axiomcode mcpcompletesinitializeandtools/list. The solve then stops at the expected❌ no engine for java@687b027f…, which is the gap already tracked in #454/#904/#1225.1. The linked command fails in PowerShell and cmd with the default Git for Windows PATH
Now tracked in #1229, with a tested fix (a Node launcher as the
binentry). The notes below are kept for context.npm's generated shims (
%APPDATA%\npm\axiomcode.ps1and.cmd) runbash.exe. The default Git for Windows install puts onlyGit\cmdon PATH, notGit\bin, so:PS> axiomcode --help & : The term 'bash.exe' is not recognized as the name of a cmdlet, function, script file, or operable program. At C:\Users\<user>\AppData\Roaming\npm\axiomcode.ps1:24 char:7The same command works in Git Bash. The README does require "a POSIX shell (Git Bash on Windows)", but a user who runs
npm linkornpm i -gand then opens PowerShell gets a message that points at neither axiomcode nor the fix. Possible fixes: document "run from Git Bash, or addC:\Program Files\Git\binto PATH", or ship a small Nodebinlauncher that locates Git's bash (the MCP config pathnpx -y @axiomcode/code-graph mcpwill hit the same thing).Also note that on a machine that has WSL,
bash.exeresolves toC:\Windows\System32\bash.exe, which is WSL bash and not Git Bash, so the shim then runs the pipeline under a different environment. This part is unverified here because WSL isn't installed.2. The "no engine" message has no Windows route
graph/pipeline/run-souffle.sh:450:• install souffle 2.5 to compile locally (macOS: brew install souffle; Ubuntu: the .deb from souffle-lang/souffle releases).On Windows neither hint applies, and there is no packaged Soufflé 2.5 for native Windows. So until the engine packages are published, the message leaves a Windows developer with no actionable step. A Windows line (WSL2, or the MSYS2/MinGW route described in the comment above) would close that.
Summary
src/pipeline/run-souffle.shcannot run on Windows outside WSL/Cygwin. Soufflé itself is not the blocker, it builds natively with MSVC and is CI-tested on Windows. The gaps are in our pipeline: Unix-only header discovery, symlinked fact staging, and bash.Soufflé already supports Windows
souffle-lang/souffleVS-CI-Tests.ymlbuilds onwindows-2025with MSVC via vcpkg + chocolatey, currently targeting Visual Studio 18 2026.run-souffle.sh:268:So partial Windows awareness exists; it's just incomplete.
What actually blocks it
1. Soufflé header discovery is Unix-prefix only,
run-souffle.sh:56-59A vcpkg install lands at
$VCPKG_ROOT/installed/x64-windows/include/souffle, which is never searched.AXIOM_SOUFFLE_INCLUDEoverrides it, so it works with one env var, it just isn't discovered, and the failure message reads like a broken install.2. Library fact staging symlinks, the cached library signatures are
ln -s'd into the facts dir. Native Windows needs Developer Mode or elevation for symlinks; Cygwin/MSYS are fine. A copy fallback whenln -sfails would be enough.3. The pipeline is bash, fine under WSL2, Cygwin, MSYS2, Git Bash; not under
cmd/PowerShell.Proposed scope
Two options, worth deciding before implementing:
run-souffle.sh, add vcpkg/MSVC prefixes to the header search, fall back tocpwhenln -sfails, extend the existinguname -scase. Keeps one script; the divergence is genuinely small.run-souffle-windows.ps1, cleaner separation, but duplicates the staging/caching logic, which is the part most likely to drift.Leaning (a), the only real differences are prefix search and symlink-vs-copy, and the Cygwin branch is already there.
Acceptance
test/python/run-tests.shpasses on Windows (WSL2 and at least one of Cygwin/MSYS2)AXIOM_SOUFFLE_INCLUDENotes
Nothing in the test suites is a blocker: verified no bash-4 features, no BSD/GNU-specific commands (
md5vsmd5sum,sed -i '',stat -f), no absolute paths, and the suite passes from an arbitrary CWD and from a path containing spaces.