Every launcher that calls the build stamp gets the 8 MB main stack (#2940)

The Windows main-thread reserve was set only in `crates/benilla/build.rs`. So `benilla-worldview`, and every launcher built on benilla by the recipe in `extended_launcher.rs`, linked with the linker's default: 1 MB on MSVC, 2 MB on mingw. That is less than the login screen needs.

`benilla_buildstamp::emit()`, which every launcher's build script already calls, now asks for 8 MB through `rustc-link-arg`: `/STACK` on MSVC, `--stack` on mingw. Unlike `rustc-link-arg-bins`, cargo accepts it from a package with no binary, so a library that calls `emit()` still builds. The recipe and CONTRIBUTING's "Building on top" say so.

Test, mingw cross-build: `SizeOfStackReserve` is 0x800000 for `benilla.exe` (unchanged) and `benilla-worldview.exe` (was 0x200000). A library-only crate calling `emit()` checks clean on MSVC and builds on mingw.

Fixes #2934

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sam Sung
2026-11-01 08:17:05 +07:00
committed by GitHub
parent 383cf93c00
commit 514b7d32b2
5 changed files with 70 additions and 31 deletions
@@ -10,8 +10,9 @@
//! `AddOns/` folder in [`benilla_app::config_dir`], where an addon the crate ships is written.
//!
//! A crate of its own copies the `benilla` launcher (`crates/benilla`): a build script that stamps
//! its commit, a `main` like this one that reads the stamp back as `crates/benilla/src/main.rs`
//! does, and a manifest that takes benilla as the workspace does:
//! its commit and gives its binary the 8 MB main-thread stack the app needs on Windows, a `main`
//! like this one that reads the stamp back as `crates/benilla/src/main.rs` does, and a manifest
//! that takes benilla as the workspace does:
//!
//! ```toml
//! [features]
@@ -45,7 +46,7 @@
//! ```
//!
//! ```text
//! // build.rs
//! // build.rs: the stamp, and on Windows the main-thread stack of every binary in the package
//! fn main() {
//! benilla_buildstamp::emit();
//! }
+57 -6
View File
@@ -1,14 +1,20 @@
//! The build stamp: the `build.rs` body the `benilla` and `benilla-worldview` launcher shims
//! share. It stays a build-dependency of the shims, never of `benilla-app`: cargo rebuilds the
//! package that owns a `rerun-if-changed` path whenever that path's mtime moves, and these paths
//! move on every commit, rebase and checkout.
//! share, and every launcher a crate on top builds the same way. It stays a build-dependency of
//! the shims, never of `benilla-app`: cargo rebuilds the package that owns a `rerun-if-changed`
//! path whenever that path's mtime moves, and these paths move on every commit, rebase and
//! checkout.
use std::path::Path;
use std::process::Command;
/// Stamp the commit this binary was built from into the calling package as six `rustc-env`
/// vars, which its `main.rs` reads back with `env!` into a `BuildId`, beside its own
/// `CARGO_PKG_VERSION`:
/// The main thread's stack on Windows, in bytes: what Linux and macOS give it.
const MAIN_THREAD_STACK: u32 = 8 << 20;
/// Stamp the commit this binary was built from into the calling package, and give its executables
/// the main-thread stack the app needs on Windows.
///
/// The stamp is six `rustc-env` vars, which its `main.rs` reads back with `env!` into a
/// `BuildId`, beside its own `CARGO_PKG_VERSION`:
///
/// - `BENILLA_GIT_SHA`: the full sha.
/// - `BENILLA_GIT_SHORT`: git's own abbreviation of it.
@@ -94,6 +100,34 @@ pub fn emit() {
String::new()
};
println!("cargo::rustc-env=BENILLA_PROJECT_DIR={project}");
main_thread_stack();
}
/// The app runs on the main thread, where the windowing event loop must live, and needs more than
/// the 1 MB the MSVC linker reserves for it by default (2 MB on mingw): the login screen overflows
/// it. So the package's Windows executables reserve [`MAIN_THREAD_STACK`] through
/// `rustc-link-arg`, which cargo passes to every target it links and, unlike
/// `rustc-link-arg-bins`, takes from a package that has no binary.
fn main_thread_stack() {
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
if let Some(arg) = stack_link_arg(&os, &env) {
println!("cargo::rustc-link-arg={arg}");
}
}
/// The linker's spelling of the main-thread reserve for a target: `/STACK` on MSVC, `--stack`
/// through the compiler driver on mingw; none off Windows, where the system gives 8 MB already.
fn stack_link_arg(target_os: &str, target_env: &str) -> Option<String> {
if target_os != "windows" {
return None;
}
match target_env {
"msvc" => Some(format!("/STACK:{MAIN_THREAD_STACK}")),
"gnu" => Some(format!("-Wl,--stack,{MAIN_THREAD_STACK}")),
_ => None,
}
}
/// The workspace root of the package at `manifest_dir`: the nearest folder up from it holding
@@ -135,6 +169,23 @@ mod tests {
std::fs::remove_dir_all(&bare).ok();
}
/// A Windows binary reserves 8 MB for its main thread in the linker's own spelling; no other
/// target is asked to.
#[test]
fn a_windows_binary_reserves_the_main_threads_8_mb() {
assert_eq!(
stack_link_arg("windows", "msvc").as_deref(),
Some("/STACK:8388608")
);
assert_eq!(
stack_link_arg("windows", "gnu").as_deref(),
Some("-Wl,--stack,8388608")
);
for (os, env) in [("macos", ""), ("linux", "gnu"), ("windows", "")] {
assert_eq!(stack_link_arg(os, env), None, "{os} {env}");
}
}
/// benilla's own launchers stamp the checkout, the folder a dev build always used.
#[test]
fn a_launcher_in_this_checkout_stamps_the_checkout() {
+2 -1
View File
@@ -1,4 +1,5 @@
//! Stamps the commit this binary was built from.
//! Stamps the commit this binary was built from, and gives the main thread the stack the app needs
//! on Windows.
fn main() {
benilla_buildstamp::emit();
+2 -17
View File
@@ -1,21 +1,6 @@
//! Stamps the commit this binary was built from, and gives the main thread the stack the app needs.
//! Stamps the commit this binary was built from, and gives the main thread the stack the app needs
//! on Windows.
fn main() {
benilla_buildstamp::emit();
main_thread_stack();
}
/// The app runs on the main thread, where the windowing event loop must live, and needs more than
/// the 1 MB the MSVC linker reserves for it by default (2 MB on mingw): the login screen overflows
/// it. Linux and macOS give the main thread 8 MB, so Windows reserves the same.
fn main_thread_stack() {
const STACK: u32 = 8 << 20;
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
return;
}
match std::env::var("CARGO_CFG_TARGET_ENV").as_deref() {
Ok("msvc") => println!("cargo:rustc-link-arg-bins=/STACK:{STACK}"),
Ok("gnu") => println!("cargo:rustc-link-arg-bins=-Wl,--stack,{STACK}"),
_ => {}
}
}
+5 -4
View File
@@ -40,10 +40,11 @@ taken.
A feature 1.12.1 does not have lives in a crate of its own that adds its Bevy plugins through
`benilla_app::run_with`, as `crates/benilla-app/examples/extended_launcher.rs` shows, or in a
fork. A client started through `run_with` says `extended` in its build line, so its reports read
apart from stock benilla's. The crate keeps its own settings, never rows in benilla's CVar table,
which every addon reads, and its files go under `benilla_app::config_dir()`, its own
`benilla-config/`: an addon it ships is written into that folder's `AddOns/` and loads as any
addon does.
apart from stock benilla's. Its build script calls `benilla_buildstamp::emit()`, which stamps the
commit that line names and gives its binary the 8 MB main-thread stack the app needs on Windows.
The crate keeps its own settings, never rows in benilla's CVar table, which every addon reads,
and its files go under `benilla_app::config_dir()`, its own `benilla-config/`: an addon it ships
is written into that folder's `AddOns/` and loads as any addon does.
Past `run_with` and that folder, benilla opens a piece of itself to such a crate only when its own
1.12.1 work creates that piece, never for the crate alone, and promises no stable API: a crate