Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .github/workflows/prebuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ jobs:
arch: ${{ matrix.msvcArch }}

- name: Build prebuild
env:
RUSTFLAGS: ${{ runner.os == 'Windows' && '-C target-feature=+crt-static' || '' }}
run: npx napi build --platform --release --target "${{ matrix.rustTarget }}" --js false
working-directory: packages/native

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ jobs:
arch: ${{ matrix.msvcArch }}

- name: Build prebuild
env:
RUSTFLAGS: ${{ runner.os == 'Windows' && '-C target-feature=+crt-static' || '' }}
run: npx napi build --platform --release --target "${{ matrix.rustTarget }}" --js false
working-directory: packages/native

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ The format is based on Keep a Changelog and the project follows Semantic Version

### Fixed

- **native/windows**: Windows prebuilt native binaries now link the MSVC runtime
statically, so packaged apps can run on clean Windows installs without a
separate Visual C++ Redistributable install.
- **core/renderer**: Per-side box borders now render standalone edges. A box with only `borderLeft` (or any vertical-only side combination) previously drew nothing when shorter than 2 rows; corner rows are now only required when a horizontal edge is present. Markdown blockquotes use this to render a GitHub-style dim left bar instead of a rounded box.

## [0.1.0-beta.3] - 2026-06-11
Expand Down
2 changes: 2 additions & 0 deletions docs/backend/native.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ combinations:
When you install `@rezi-ui/native`, the correct prebuilt binary is selected
automatically based on `process.platform` and `process.arch`. No postinstall
scripts are required -- the binary is included directly in the published package.
Windows prebuilds are linked with the static MSVC C runtime, so they do not
require users to install the Visual C++ Redistributable separately.

Prebuilt binaries are produced by the `prebuild.yml` GitHub Actions workflow,
which cross-compiles on CI for all target triples.
Expand Down
3 changes: 3 additions & 0 deletions docs/getting-started/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ No install-time source build runs for unsupported targets. If a matching
prebuilt binary is not available, build from a repository checkout with
`npm run build:native`.

Windows prebuilt binaries do not require a separate Visual C++ Redistributable
install.

## Package Overview

| Package | Description | Required |
Expand Down
1 change: 1 addition & 0 deletions docs/packages/native.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Native addon:
- Rust + `napi-rs` binding
- vendored Zireael engine sources
- prebuilt `.node` binaries for supported targets
- static MSVC runtime linking for Windows prebuilds

## Install

Expand Down
2 changes: 2 additions & 0 deletions packages/native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ npm run check:native-vendor
- Engine placement is controlled by `@rezi-ui/node` `executionMode` (`auto` | `worker` | `inline`).
- `executionMode: "auto"` selects inline when `fpsCap <= 30`, worker otherwise.
- All buffers across the boundary are caller-owned; binary formats are validated strictly.
- Windows builds link the MSVC C runtime statically so packaged apps do not need
a separate Visual C++ Redistributable install.
- Native compilation reads `packages/native/vendor/zireael` (not `vendor/zireael`).
- `packages/native/vendor/VENDOR_COMMIT.txt` must match the `vendor/zireael` gitlink commit.

Expand Down
7 changes: 7 additions & 0 deletions packages/native/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ fn main() {
build.include(&src_dir);
build.warnings(false);

let target_features = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
if env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc")
&& target_features.split(',').any(|feature| feature == "crt-static")
{
build.static_crt(true);
}

// The engine assumes a C99-or-newer compiler; C11 is required for atomics on MSVC.
if build.get_compiler().is_like_msvc() {
build.flag_if_supported("/std:c11");
Expand Down
26 changes: 25 additions & 1 deletion packages/native/scripts/build-native.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { copyFileSync, existsSync, readdirSync, rmSync, writeFileSync } from "no
import { tmpdir } from "node:os";
import { delimiter, join } from "node:path";

const WINDOWS_STATIC_CRT_RUSTFLAGS = "-C target-feature=+crt-static";

function splitPathList(value, delim) {
return value
.split(delim)
Expand Down Expand Up @@ -338,6 +340,25 @@ function withRustToolchainOnPath(env) {
return next;
}

function isWindowsMsvcTarget(targetTriple) {
return typeof targetTriple === "string" && targetTriple.endsWith("-pc-windows-msvc");
}

function withWindowsStaticCrtEnv(env, hostTargetTriple) {
if (process.platform !== "win32" || !isWindowsMsvcTarget(hostTargetTriple)) return env;

const current = typeof env.RUSTFLAGS === "string" ? env.RUSTFLAGS.trim() : "";
if (/(^|\s)target-feature=\+crt-static(\s|$)/.test(current)) return env;
if (/(^|\s)-Ctarget-feature=\+crt-static(\s|$)/.test(current)) return env;

const next = { ...env };
next.RUSTFLAGS =
current.length > 0
? `${current} ${WINDOWS_STATIC_CRT_RUSTFLAGS}`
: WINDOWS_STATIC_CRT_RUSTFLAGS;
return next;
}

function buildWithCargoDirectly(env, host) {
const cargoExe = getCargoExePath(env) ?? "cargo";
try {
Expand Down Expand Up @@ -405,7 +426,10 @@ try {
}

const scriptEnv = ensureCargoOnPath(
withWindowsSdkEnv(withMsvcDevEnv(withRustToolchainOnPath(process.env), host), host),
withWindowsStaticCrtEnv(
withWindowsSdkEnv(withMsvcDevEnv(withRustToolchainOnPath(process.env), host), host),
host,
),
);

// @napi-rs/cli parses Cargo.toml by running `cargo metadata` through cmd.exe on Windows.
Expand Down
64 changes: 64 additions & 0 deletions scripts/__tests__/native-windows-static-crt.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { strict as assert } from "node:assert";
import { readFileSync } from "node:fs";
import { describe, test } from "node:test";
import { fileURLToPath } from "node:url";

const BUILD_RS_PATH = fileURLToPath(new URL("../../packages/native/build.rs", import.meta.url));
const BUILD_NATIVE_SCRIPT_PATH = fileURLToPath(
new URL("../../packages/native/scripts/build-native.mjs", import.meta.url),
);
const PREBUILD_WORKFLOW_PATH = fileURLToPath(
new URL("../../.github/workflows/prebuild.yml", import.meta.url),
);
const RELEASE_WORKFLOW_PATH = fileURLToPath(
new URL("../../.github/workflows/release.yml", import.meta.url),
);

const STATIC_CRT_RUSTFLAGS = "-C target-feature=+crt-static";

function assertWindowsPrebuildUsesStaticCrt(workflow, label) {
assert.match(
workflow,
new RegExp(
[
"- name: Build prebuild",
"[\\s\\S]*?env:",
`[\\s\\S]*?RUSTFLAGS: \\$\\{\\{ runner\\.os == 'Windows' && '${STATIC_CRT_RUSTFLAGS.replace("+", "\\+")}' \\|\\| '' \\}\\}`,
'[\\s\\S]*?run: npx napi build --platform --release --target "\\$\\{\\{ matrix\\.rustTarget \\}\\}" --js false',
].join("\\n?"),
"u",
),
`${label} Windows prebuilds must pass Rust +crt-static`,
);
}

describe("native Windows CRT linking", () => {
test("local Windows native builds use static MSVC CRT", () => {
const buildRs = readFileSync(BUILD_RS_PATH, "utf8");
const buildScript = readFileSync(BUILD_NATIVE_SCRIPT_PATH, "utf8");

assert.match(buildRs, /CARGO_CFG_TARGET_ENV"\)\.as_deref\(\) == Ok\("msvc"\)/);
assert.match(
buildRs,
/target_features\.split\(','\)\.any\(\|feature\| feature == "crt-static"\)/,
);
assert.match(buildRs, /build\.static_crt\(true\)/);
assert.match(
buildScript,
/const WINDOWS_STATIC_CRT_RUSTFLAGS = "-C target-feature=\+crt-static";/,
);
assert.match(buildScript, /function isWindowsMsvcTarget\(targetTriple\)/);
assert.match(buildScript, /function withWindowsStaticCrtEnv\(env, hostTargetTriple\)/);
});

test("published Windows native prebuilds use static MSVC CRT", () => {
assertWindowsPrebuildUsesStaticCrt(
readFileSync(PREBUILD_WORKFLOW_PATH, "utf8"),
"manual prebuild workflow",
);
assertWindowsPrebuildUsesStaticCrt(
readFileSync(RELEASE_WORKFLOW_PATH, "utf8"),
"release workflow",
);
});
});
Loading