Skip to content

feat: native Windows support - #41

Open
jhodges10 wants to merge 4 commits into
fiberplane:mainfrom
hodgesbadgecompany:windows-native-builds
Open

feat: native Windows support#41
jhodges10 wants to merge 4 commits into
fiberplane:mainfrom
hodgesbadgecompany:windows-native-builds

Conversation

@jhodges10

Copy link
Copy Markdown

Closes #35.

I have a developer on Windows, so I took you up on the offer to send a PR. drift already compiled on Windows — what it didn't do was produce a drift.lock that meant the same thing anywhere else. On a fresh Windows checkout of this repo, drift check reported every doc stale.

What was broken

Path separators. Repo-relative paths come out of std.Io.Dir.path.relative with host separators, so drift link docs/a.md src/b.ts recorded doc = "docs\\a.md". Doc discovery matches bindings against git ls-files, which is POSIX on every platform, so a binding never matched the doc it was written for.

Line endings. Git for Windows enables core.autocrlf by default, so the working tree holds CRLF where Linux holds LF. Every fingerprint that reaches raw bytes moved with the checkout rather than the content — the no-grammar fallback and markdown sections directly, and grammar-based fingerprints too, since a line comment's token text runs to end-of-line and swallows the \r.

Together these accounted for all 26 integration-test failures on Windows.

A relink-gate use-after-free. Chasing the above turned up a real bug that isn't Windows-specific. Binding.setField frees the previous value, so the old_sig slice the gate holds across refreshBindingSig dangles, and isDocGateBlocked compares against freed memory. Whether a relink is refused or waved through depends on what the allocator left behind. On Windows it meant drift link <doc> refused every anchor in a doc, including ones whose fingerprint had not moved. It's the second commit and stands alone if you'd rather take it separately.

What's here

  • src/repo_path.zig — normalizes a repo-relative path to / where it is produced. Absolute paths keep host form; they never leave the process.
  • src/content.zig — collapses CRLF as working-tree files are read. LF-only content hashes unchanged, so existing lockfiles stay valid — no relink needed for anyone on Linux or macOS.
  • .gitattributes pinning the working tree to LF. drift no longer needs it, but zig fmt rejects CRLF, so without it a Windows contributor can't format the repo. Content is already LF in the index, so git add --renormalize . is a no-op here.
  • A test-windows CI job on windows-latest running the same build, test suite, and drift lint as the Linux job. Cross-compiling Windows without ever running it is what let this sit.
  • x86_64-windows and aarch64-windows in the CI and release matrices, cross-compiled from Linux like the other targets, packaged as .zip since Windows opens those without extra tooling.
  • Regression tests that fail without the fixes: a CRLF-vs-LF freshness test (covering a grammar-backed file, a no-grammar file, and a markdown heading) and a lockfile-separator test. Both run on every platform.
  • Docs: Decision 15, a DESIGN section on cross-platform identity, a README install section for Windows, RELEASING and CLI updates.

Verification

On Windows 11, Zig 0.16.0:

Build Summary: 11/11 steps succeeded; 81/81 tests passed
drift check                      → exit 0
zig build gen-check-schema       → schema matches what's committed

Both Windows targets cross-compile clean. Test count went 79 → 81 (the 26 Windows failures are gone; the added tests are net-new).

I have not touched the install.sh at drift.fp.dev, so the README points Windows users at the release zip and at building from source. Happy to follow up there if you want it to handle Windows too.

🤖 Generated with Claude Code

jhodges10 and others added 3 commits August 24, 2026 12:53
drift compiled on Windows already, but nothing it wrote there was portable:
`drift check` reported every doc in the repo stale.

Two things differed. Repo-relative paths came out of `std.Io.Dir.path.relative`
with host separators, so a binding stored as `docs\a.md` never matched the
`docs/a.md` that `git ls-files` reports during doc discovery. And Git for
Windows turns on `core.autocrlf` by default, so the working tree holds CRLF
where Linux holds LF, and every fingerprint that reaches raw bytes changed with
the checkout rather than the content — the no-grammar fallback and markdown
sections directly, and grammar-based fingerprints too, since a line comment's
token text runs to end-of-line and swallows the CR.

Normalize both at the boundary: `repo_path.normalize` rewrites a repo-relative
path to POSIX separators where it is produced, and `content.normalizeLineEndings`
collapses CRLF as working-tree files are read. Absolute paths keep host form —
they never leave the process. LF-only content hashes unchanged, so lockfiles
written before this stay valid.

Also point the integration tests at `drift.exe` explicitly rather than relying
on CreateProcess probing for the extension.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Binding.setField` frees the previous value, so the `old_sig` slice the relink
gate holds across `refreshBindingSig` dangles, and `isDocGateBlocked` compares
against freed memory. Whether a relink is refused or waved through then depends
on what the allocator happened to leave behind — on Windows, `drift link <doc>`
refused every anchor in the doc, including ones whose fingerprint had not moved.

Dupe the signature into the run arena before restamping.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a `test-windows` job on `windows-latest` running the same build, test
suite, and `drift lint` as the Linux job. Windows is the one platform where
path separators, line endings, and the executable suffix differ, so
cross-compiling it without ever running it is what let those bugs sit.

Adds `x86_64-windows` and `aarch64-windows` to the CI and release build
matrices, cross-compiled from Linux as the other targets are. They ship as
`.zip` rather than `.tar.gz` — Windows opens a zip without extra tooling.

`.gitattributes` pins the working tree to LF. drift itself no longer cares
after the previous commits, but `zig fmt` rejects CRLF, so without this a
Windows contributor cannot format the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jhodges10

Copy link
Copy Markdown
Author

@laulauland I missed your initial response to my github issue, but I just went ahead and did the Windows build!

- content: skip CRLF normalization for binary-looking content (git's
  NUL-in-first-8000-bytes heuristic) so a CR-only change in a raw-hash
  target stays detectable, and early-out on buffers with no CR
- docs: qualify the lockfile-compatibility claim (committed-CRLF text
  re-fingerprints once and needs a relink), document the binary
  carve-out, and collapse DESIGN.md's restatement of Decision 15
- release workflow: fail loudly when an archive goes missing
  (if-no-files-found: error, fail_on_unmatched_files) instead of
  publishing a release without it
- ci: build matrix no longer waits on test-windows (it still gates
  merges as its own status check); strict artifact upload
- README: arch-aware Windows download, registry-safe PATH append that
  preserves REG_EXPAND_SZ entries and dedupes, from-source install
  that lands on PATH
- link: hoist the doc read out of the normalize call argument; drop a
  redundant lockfile assertion in the integration test
- relink drift anchors for the updated docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native Windows builds

1 participant