Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 71 additions & 0 deletions .github/workflows/verify-windows-shim.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: verify windows shim

# Rebuilds the Windows shim from dotslash_windows_shim.rs and fails if the
# checked-in .exe does not match, so the source cannot change without the
# regenerated binary. The build is byte-for-byte reproducible (rust-lld with
# /Brepro, plus the pinned toolchain in windows_shim/rust-toolchain.toml), so a
# plain `git diff` is a reliable check. Each architecture is built on its own
# native runner to avoid cross-linking.
#
# The freshly built binary is uploaded as an artifact before the diff check, so
# when the check fails (e.g. the committed binaries are out of date) you can
# download the correct binary from the run's "Artifacts" section and commit it
# without needing a local Windows machine.

on:
push:
branches: [main]
paths:
- windows_shim/**
- .github/workflows/verify-windows-shim.yml
pull_request:
paths:
- windows_shim/**
- .github/workflows/verify-windows-shim.yml
workflow_dispatch:

permissions:
contents: read

jobs:
verify-windows-shim:
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
arch: x86_64
- os: windows-11-arm
target: aarch64-pc-windows-msvc
arch: aarch64
runs-on: ${{ matrix.os }}
timeout-minutes: 20
defaults:
run:
shell: bash
working-directory: windows_shim
steps:
- uses: actions/checkout@v6
- name: Provision the pinned toolchain and target
# `cargo --version` auto-installs the nightly pinned by
# rust-toolchain.toml; the target then provisions its precompiled std.
run: |
cargo --version
rustup target add "${{ matrix.target }}"
- name: Rebuild the shim
run: python release.py "${{ matrix.target }}"
- name: Upload the rebuilt shim
# Runs before the diff check so the binary is downloadable even when the
# committed copy is out of date and the job ultimately fails.
uses: actions/upload-artifact@v4
with:
name: dotslash_windows_shim-${{ matrix.arch }}
path: windows_shim/dotslash_windows_shim-${{ matrix.arch }}.exe
if-no-files-found: error
- name: Verify the checked-in binary is up to date
run: |
if ! git diff --exit-code -- 'dotslash_windows_shim-*.exe'; then
echo "::error::Checked-in shim binary is out of date. Download the 'dotslash_windows_shim-${{ matrix.arch }}' artifact from this run and commit it (or run 'py release.py' locally)."
exit 1
fi
5 changes: 3 additions & 2 deletions website/docs/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ of this documentation.

### DotSlash Windows Shim

**This is the preferred method.** The _DotSlash Windows Shim_ is a tiny 4KB
`.exe` executable that is placed next to the DotSlash file that performs the
**This is the preferred method.** The _DotSlash Windows Shim_ is a tiny
(a few kilobytes) `.exe` executable that is placed next to the DotSlash file
that performs the
same function as the [batch script](#sibling-batch-script) above, but is a
native executable rather than a batch script. This is the _ideal_ method that
allows for easy execution without any of the drawbacks of batch scripts. But
Expand Down
33 changes: 32 additions & 1 deletion windows_shim/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,45 @@ The _DotSlash Windows Shim_ does this:
## Binary size

_DotSlash Windows Shim_ builds without a standard library and only uses Windows
APIs. Release binaries are around ~5KB.
APIs to stay small. It is meant to be checked into source control next to every
DotSlash file that needs to run on Windows, so the release binaries are only a
few kilobytes.

## Release

The checked-in `dotslash_windows_shim-x86_64.exe` and
`dotslash_windows_shim-aarch64.exe` are built from `dotslash_windows_shim.rs`.
Regenerate them on Windows with:

```shell
py release.py
```

Building both architectures requires their targets to be installed
(`rustup target add x86_64-pc-windows-msvc aarch64-pc-windows-msvc`). Pass a
single target triple to build just one architecture:

```shell
py release.py aarch64-pc-windows-msvc
```

The build is byte-for-byte reproducible. It links with the Rust-bundled
`rust-lld` — which, unlike MSVC's `link.exe`, embeds no toolchain-specific
"Rich" header — and passes `/Brepro` so timestamps are content hashes rather
than wall-clock time. The output therefore depends only on the toolchain pinned
in `rust-toolchain.toml`. The `verify windows shim` GitHub Actions workflow
rebuilds the shim whenever anything under `windows_shim/` changes and fails if
the committed binaries are stale, so regenerate and commit them in the same
change as any edit to the source or a bump of the pinned toolchain.

If you do not have a Windows machine, let CI build the binaries for you: push
your change (or trigger the workflow manually), then download the
`dotslash_windows_shim-x86_64` and `dotslash_windows_shim-aarch64` artifacts
from the workflow run — each contains the freshly built `.exe`. Because the
build is reproducible, those artifacts are exactly what a local `py release.py`
would produce; commit them into `windows_shim/` and re-run the workflow to
confirm it passes.

## Testing

```shell
Expand Down
Binary file modified windows_shim/dotslash_windows_shim-aarch64.exe
Binary file not shown.
Binary file modified windows_shim/dotslash_windows_shim-x86_64.exe
Binary file not shown.
51 changes: 43 additions & 8 deletions windows_shim/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,27 @@
import os
import shutil
import subprocess
import sys
from pathlib import Path

IS_WINDOWS: bool = os.name == "nt"

target_triplets: list[str] = ["x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"]


def main() -> None:
def main(targets: list[str] | None = None) -> None:
if not IS_WINDOWS:
raise Exception("Only Windows is supported.")

# Default to all targets; a caller (e.g. CI) may pass a subset to build just
# the target that matches the current runner's architecture.
if targets:
unknown = [t for t in targets if t not in target_triplets]
if unknown:
raise SystemExit(f"Unknown target(s): {', '.join(unknown)}")
else:
targets = target_triplets

dotslash_windows_shim_root = Path(os.path.realpath(__file__)).parent

target_dir = (
Expand All @@ -30,7 +40,36 @@ def main() -> None:
else None
)

for triplet in target_triplets:
# Link with the linker bundled in the active Rust toolchain (rust-lld)
# instead of the MSVC link.exe. lld-link emits no "Rich" header, so the
# output depends only on the pinned toolchain (see rust-toolchain.toml) and
# not on whichever Visual Studio version happens to be installed. Together
# with /Brepro below this keeps the checked-in binaries byte-for-byte
# reproducible, which the verify-windows-shim CI job relies on.
target_libdir = Path(
subprocess.check_output(["rustc", "--print", "target-libdir"], text=True).strip()
)
rust_lld = target_libdir.parent / "bin" / "rust-lld.exe"
if not rust_lld.is_file():
raise FileNotFoundError(f"Rust's bundled linker was not found: {rust_lld}")

rustflags = [
f"-Clinker={rust_lld}",
"-Clinker-flavor=lld-link",
"-Clink-arg=/DEBUG:NONE", # Avoid an embedded PDB path.
"-Clink-arg=/NODEFAULTLIB:msvcrt", # The shim does not use the CRT.
"-Clink-arg=/Brepro", # Hash-based timestamps instead of wall-clock time.
]

# Ambient RUSTFLAGS could change the measured release layout and break
# reproducibility. Encoded flags also preserve the linker path as a single
# argument when the workspace path contains spaces.
build_env = {**os.environ}
build_env.pop("RUSTFLAGS", None)
build_env["RUSTC_BOOTSTRAP"] = "1" # Required by no_std language items.
build_env["CARGO_ENCODED_RUSTFLAGS"] = "\x1f".join(rustflags)

for triplet in targets:
subprocess.run(
[
"cargo",
Expand All @@ -43,11 +82,7 @@ def main() -> None:
f"--target={triplet}",
],
check=True,
env={
**os.environ,
"RUSTC_BOOTSTRAP": "1",
"RUSTFLAGS": "-Clink-arg=/DEBUG:NONE", # Avoid embedded pdb path
},
env=build_env,
)

src = (
Expand All @@ -64,4 +99,4 @@ def main() -> None:


if __name__ == "__main__":
main()
main(sys.argv[1:])
6 changes: 5 additions & 1 deletion windows_shim/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
[toolchain]
channel = "nightly"
# Pinned so the checked-in shim binaries stay byte-for-byte reproducible: the
# CI job in .github/workflows/verify-windows-shim.yml rebuilds the shim and
# fails if the result differs from what is committed. Bump this and regenerate
# the binaries (`py release.py`) in the same change.
channel = "nightly-2026-07-28"
Loading