# Building Windows (/project/building-windows)

<!-- agent-signals: reading_time_min: 5 · est_tokens: 2013 · updated: 2026-09-23 -->
Related: [Roadmap](/project/roadmap.md), [Benchmarking](/project/benchmarking.md), [Contributing](/project/contributing.md), [Bindgen](/project/bindgen.md), [License](/project/license.md)

Use [PowerShell 7 (`pwsh.exe`)](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4) instead of the default `powershell.exe`. If you run into problems, ask in the [#contributing channel on our Discord](http://bun.com/discord).

## Prerequisites [#prerequisites]

### Enable Scripts [#enable-scripts]

By default, running unverified scripts is blocked.

```ps1
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted
```

### System Dependencies [#system-dependencies]

Bun v1.1 or later. The build uses Bun to run its own code generators.

```ps1
irm bun.sh/install.ps1 | iex
```

[Visual Studio](https://visualstudio.microsoft.com) with the "Desktop Development with C++" workload. While installing, also install Git if Git for Windows is not already installed.

Install Visual Studio with the graphical wizard or through WinGet:

```ps1
winget install "Visual Studio Community 2022" --override "--add Microsoft.VisualStudio.Workload.NativeDesktop Microsoft.VisualStudio.Component.Git " -s msstore
```

After Visual Studio, you need the following:

* LLVM 23.1.1
* Go
* Rust (via rustup)
* NASM
* Perl
* Ruby
* Node.js

<Note>
  rustup installs the Rust nightly toolchain pinned in 

  `rust-toolchain.toml`

   on the first build.
</Note>

Use [Scoop](https://scoop.sh) to install these remaining tools.

```ps1 title="Scoop (x64)"
irm https://get.scoop.sh | iex
scoop install nodejs-lts go rustup nasm ruby perl ccache
# scoop seems to be buggy if you install llvm and the rest at the same time
scoop install llvm@23.1.1
```

For Windows ARM64, download LLVM 23.1.1 directly from GitHub releases:

```ps1 title="ARM64"
# Download and install LLVM for ARM64
Invoke-WebRequest -Uri "https://github.com/llvm/llvm-project/releases/download/llvmorg-23.1.1/LLVM-23.1.1-woa64.msi" -OutFile "$env:TEMP\LLVM-23.1.1-woa64.msi"
Start-Process msiexec.exe -ArgumentList "/i `"$env:TEMP\LLVM-23.1.1-woa64.msi`" /qn" -Wait
```

<Note>
  Do not install these with WinGet or another package manager: you will likely get Strawberry Perl instead of a more
  minimal installation of Perl. Strawberry Perl adds many other utilities to `$Env:PATH` that conflict with MSVC and
  break the build.
</Note>

To build WebKit locally (optional, x64 only), install these packages:

```ps1 title="Scoop"
scoop install make cygwin python
```

<Note>
  ARM64 builds do not need Cygwin because WebKit is provided as a pre-built binary.
</Note>

From here on out, **use a PowerShell terminal with `.\scripts\vs-shell.ps1` sourced**. Load the script by running it:

```ps1
.\scripts\vs-shell.ps1
```

To verify, check for an MSVC-only command such as `mt.exe`:

```ps1
Get-Command mt
```

<Note>
  Avoid installing `ninja` / `cmake` into your global path: you may end up building Bun without `.\scripts\vs-shell.ps1`
  sourced.
</Note>

## Building [#building]

```ps1
bun run build
```

A successful build writes `bun-debug.exe` to the `build/debug` folder.

```ps1
.\build\debug\bun-debug.exe --revision
```

Add this folder to `$Env:PATH`: open the Start menu, type "Path", and use the environment variables menu to add `C:\.....\bun\build\debug` to the user environment variable `PATH`. Then restart your editor (if it still does not pick up the change, log out and log back in).

## Extra paths [#extra-paths]

* The build extracts WebKit to `$Env:BUN_INSTALL\build-cache\webkit-<version>-debug` (`webkit-<version>-arm64-debug` on ARM64); `BUN_INSTALL` defaults to `~\.bun`. Set `$Env:BUN_BUILD_CACHE_DIR` to put the build cache somewhere else.

## Tests [#tests]

Run the test suite with `bun-debug test <path>` or with the wrapper script `bun run test <path>`. The `bun run test` command runs every test file in a separate instance of `bun-debug.exe`, so a crash in the test runner does not stop the entire suite.

```ps1
# Run the entire test suite with reporter
# the package.json script "test" uses "build/debug/bun-debug.exe" by default
bun run test

# Run an individual test file:
bun-debug test node\fs
bun-debug test "C:\bun\test\js\bun\resolve\import-meta.test.js"
```

## Troubleshooting [#troubleshooting]

### .rc file fails to build [#rc-file-fails-to-build]

`llvm-rc.exe` is odd; don't use it. Use `rc.exe` instead: make sure you are in a Visual Studio dev terminal, and check `rc /?` to confirm it is `Microsoft Resource Compiler`.

### failed to write output 'bun-debug.exe': permission denied [#failed-to-write-output-bun-debugexe-permission-denied]

You cannot overwrite `bun-debug.exe` while it is open. You likely have a running instance, maybe in the VS Code debugger.

## Cross-compiling from Linux [#cross-compiling-from-linux]

You can also build Windows binaries (both x64 and arm64) on a Linux host. The build uses the host LLVM's `clang-cl`, `lld-link`, `llvm-lib` and `llvm-rc` (part of every LLVM distribution). For headers and import libraries, the build also uses an "xwin splat" of the MSVC CRT/STL and Windows SDK.

### Prerequisites [#prerequisites-1]

1. The same LLVM version a native build uses (`pins.llvm` in `scripts/build/ci-images/spec.ts`), installed so that `clang-cl`, `lld-link`, `llvm-lib` and `llvm-rc` are available. On Debian/Ubuntu, `apt.llvm.org` packages provide all of them.
2. `nasm` (only needed for Windows x64; BoringSSL's x64 assembly is NASM syntax).
3. Rust std for the Windows targets (`rust-toolchain.toml` lists them; `rustup target add x86_64-pc-windows-msvc aarch64-pc-windows-msvc` if missing).
4. A Windows sysroot: an [xwin](https://github.com/Jake-Shadle/xwin) splat of the MSVC CRT, Windows SDK, and ATL laid out like a Visual Studio install. Downloading these components means accepting Microsoft's license terms for them.

```bash
cargo install xwin # or download a release binary
xwin --accept-license --arch x86_64,aarch64 --sdk-version 10.0.26100 --crt-version 14.44.17.14 --include-atl splat \
  --use-winsysroot-style --preserve-ms-arch-notation --include-debug-libs \
  --output /opt/winsysroot
# clang-cl/lld-link look up SDK paths as "Include"/"Lib"; the splat writes
# them lowercase, so alias both spellings (needs the same privileges as the
# splat — configure creates these itself when the directory is writable).
ln -s include "/opt/winsysroot/Windows Kits/10/Include"
ln -s lib "/opt/winsysroot/Windows Kits/10/Lib"
```

The build looks for the sysroot at `/opt/winsysroot` (or `/opt/xwin`) automatically. If the sysroot is elsewhere, set `WINDOWS_SYSROOT=<path>` or pass `--winsysroot=<path>`. A user-writable path also lets configure manage the aliases for you. Configure validates the splat at the start of every cross build. CI agents bake the same splat into their images (the `windowsSysroot` tool of `scripts/build/ci-images/spec.ts`); when an agent doesn't have one, the build fetches it into its cache dir at configure time.

### Building [#building-1]

```bash
# Debug builds
bun run build --profile=windows-x64
bun run build --profile=windows-arm64

# Release builds
bun run build --profile=windows-x64-release
bun run build --profile=windows-arm64-release
```

Output lands in `build/debug-windows-x64/bun-debug.exe`, `build/release-windows-aarch64/bun-profile.exe` + `bun.exe`, and so on. Equivalent raw flags: `bun run build --os=windows --arch=aarch64`.

The build does not run cross-compiled executables on the host (it skips the `--revision` smoke test), so test them on a Windows machine or under Wine.

### LTO [#lto]

x64 release cross builds use ThinLTO with cross-language (Rust↔C++) LTO, like every release build (`--lto=off` for faster relinks):

* Bun's C/C++ compiles with `-flto=thin`
* rustc emits LLVM bitcode (`-Clinker-plugin-lto`)
* the `bun-webkit-windows-amd64-lto` ThinLTO prebuilt is the WebKit that gets linked
* everything links with rustc's bundled `lld-link` (its LLVM is new enough to read both compilers' bitcode)

There is no LTO for arm64, because there is no `-lto` WebKit prebuilt: LLVM's CodeView emitter can't handle ARM64 NEON tuple registers during LTO codegen.
