A dev build of a crate on top of benilla finds its install, benilla-config/ and .probe-identity in its own folder, which its build stamp hands in, not in cargo's checkout of benilla (#504)

A dev build finds its project folder, where its `WoW` link, `benilla-config/` and `.probe-identity` live, from its launcher, not from a benilla crate's own `CARGO_MANIFEST_DIR`. For a crate built on top of benilla, where benilla is a git dependency, that manifest dir is cargo's checkout of benilla.

- `benilla_buildstamp::emit()` stamps `BENILLA_PROJECT_DIR`, the launcher's workspace root (the nearest folder up holding `Cargo.lock`), only when the launcher's `dev` feature is on. A player build stamps it empty and carries no source path. The shims pass it in as `BuildId::project_dir`.
- `benilla_formats::project_folder` is the one home for the folder. Each entry point records the stamped folder once, before anything resolves from it. The install's dev rung, the state folder (which keeps its step from a linked worktree to the primary checkout) and the probe identity all read it. With nothing recorded (a test, an example, a tool) it is benilla's checkout, as before, and in a player build it is `None`.
- Live `assets/ui` and the addon corpus stay on benilla's own source.
- The example launcher's header now says a dev build of a crate on top keeps these in its own folder.
- The comment above the stamp's resource names the build banner, the stamp's reader beside the panel footer.

Fixes #494
Fixes #493

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam Sung
2026-09-28 14:55:24 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 1a37909c9c
commit 6633a11d60
15 changed files with 318 additions and 47 deletions
@@ -49,8 +49,8 @@
//!
//! Its `Cargo.lock` starts as a copy of benilla's: cargo reads only the root's lockfile too, and
//! without it resolves every crate to its newest compatible version, not the one benilla is built
//! with. A dev build looks for the `WoW` link and `benilla-config/` at benilla's repo root, which
//! for a git dependency is cargo's checkout, so `WOW_DATA` and `BENILLA_HOME` name them instead.
//! with. A dev build keeps its `WoW` link, `benilla-config/` and `.probe-identity` in its own
//! folder, beside that `Cargo.lock`, as benilla's does.
use benilla_app::{AppExit, BuildId};
use bevy::prelude::*;
+1
View File
@@ -140,6 +140,7 @@ mod tests {
short: "0123456",
date: "2026-09-16",
profile: "debug",
project_dir: "",
extended: false,
};
+5 -1
View File
@@ -237,12 +237,16 @@ fn launch(build: BuildId, extend: Option<Extension<'_>>) -> AppExit {
..build
};
// A dev build's install, state folder and probe identity resolve from the launcher's folder,
// so a crate on top of benilla keeps them in its own and not in cargo's checkout of benilla.
benilla_formats::set_project_folder(build.project_dir);
// From here a panic leaves `benilla-config/Diagnostics/crash-<unix>.txt`; armed before the
// `App` exists, so a panic while plugins build is reported too.
crash::install(build);
let mut app = App::new();
// The panel footer and the preflight banner read the stamp back.
// The panel footer and the build banner read the stamp back.
app.insert_resource(build);
// Static-scene transform tracking pinned on: the default threshold re-decides every frame with
// two full scans of the rows the tracking exists to skip, and this scene is static-heavy. It
+90 -10
View File
@@ -4,8 +4,8 @@
//!
//! Resolution, in order, the same shape as [`benilla_formats::wow_data`]:
//! 1. `$BENILLA_HOME`.
//! 2. `<project folder>/benilla-config/`, dev builds only: a shipped binary must not carry the
//! build machine's source tree.
//! 2. `<project folder>/benilla-config/`, dev builds only ([`benilla_formats::project_folder`], the
//! launcher's): a shipped binary must not carry the build machine's source tree.
//! 3. `<exe dir>/benilla-config/`.
//!
//! With `$WOW_CAPTURE` set every path resolves to `None`: a capture neither reads nor writes
@@ -39,11 +39,15 @@ pub(crate) fn home() -> Option<PathBuf> {
.map(|dir| dir.join(STATE_DIR))
}
/// The project folder a dev build keeps its state in: the primary checkout, whichever worktree
/// built the binary, so every worktree shares one settings folder. Anything unexpected falls back
/// to the crate root's grandparent rather than `None`.
/// The project folder a dev build keeps its state in ([`benilla_formats::project_folder`]), through
/// [`shared_root`].
fn dev_project_root() -> Option<PathBuf> {
let here = crate::run_mode::dev_source_dir()?.ancestors().nth(2)?;
Some(shared_root(benilla_formats::project_folder()?))
}
/// `here`, or for a linked worktree its primary checkout, whichever worktree built the binary, so
/// every worktree shares one settings folder. Anything unexpected keeps `here`.
fn shared_root(here: &Path) -> PathBuf {
let dot_git = here.join(".git");
// A linked worktree: `.git` is a file pointing at `<primary>/.git/worktrees/<name>`.
if dot_git.is_file() {
@@ -59,12 +63,12 @@ fn dev_project_root() -> Option<PathBuf> {
.and_then(|d| d.parent())
{
if primary.is_dir() {
return Some(primary.to_path_buf());
return primary.to_path_buf();
}
}
}
}
Some(here.to_path_buf())
here.to_path_buf()
}
/// `benilla-config/config.toml`: the CVar overrides, the `Config.wtf` analog.
@@ -402,8 +406,7 @@ mod tests {
let h = home().expect("home() always resolves outside a capture");
assert!(h.ends_with(STATE_DIR), "{}", h.display());
let Some(here) = crate::run_mode::dev_source_dir().and_then(|d| d.ancestors().nth(2))
else {
let Some(here) = benilla_formats::project_folder() else {
// Player build: beside the binary.
let exe_dir = std::env::current_exe()
.unwrap()
@@ -430,6 +433,83 @@ mod tests {
}
}
/// A project folder that is a linked worktree keeps its state in the primary checkout, so every
/// worktree shares one; any other folder, a crate on top of benilla's included, keeps its own.
#[test]
fn the_state_root_is_the_folder_or_its_primary_checkout() {
let tmp = std::env::temp_dir().join(format!("benilla-root-{}", std::process::id()));
std::fs::remove_dir_all(&tmp).ok();
let primary = tmp.join("benilla");
std::fs::create_dir_all(primary.join(".git/worktrees/pool-3")).unwrap();
let slot = tmp.join("pool-3");
std::fs::create_dir_all(&slot).unwrap();
let gitdir = primary.join(".git/worktrees/pool-3");
std::fs::write(slot.join(".git"), format!("gitdir: {}\n", gitdir.display())).unwrap();
assert_eq!(shared_root(&slot), primary);
assert_eq!(shared_root(&primary), primary);
let hello_mod = tmp.join("hello-mod");
std::fs::create_dir_all(hello_mod.join(".git")).unwrap();
assert_eq!(shared_root(&hello_mod), hello_mod);
let plain = tmp.join("plain");
std::fs::create_dir_all(&plain).unwrap();
assert_eq!(shared_root(&plain), plain);
std::fs::remove_dir_all(&tmp).ok();
}
/// A launcher's recorded folder is where a dev build keeps its state and reads its probe
/// identity; a player build resolves neither from it. Recording is process-wide, so the parent
/// runs this same test in a child process of the test binary, which records and resolves.
#[test]
fn a_recorded_launcher_folder_holds_the_state_and_the_identity() {
const CHILD: &str = "BENILLA_TEST_LAUNCHER_FOLDER";
if let Some(dir) = std::env::var_os(CHILD) {
let dir = PathBuf::from(dir);
benilla_formats::set_project_folder(dir.to_str().unwrap());
if crate::run_mode::dev_affordances() {
assert_eq!(home(), Some(dir.join(STATE_DIR)));
assert_eq!(
crate::run_mode::declared_identity(),
Some(crate::run_mode::DeclaredIdentity {
user: "probe9".into(),
character: "Modchar".into()
})
);
} else {
let exe_dir = std::env::current_exe().unwrap();
assert_eq!(home(), Some(exe_dir.parent().unwrap().join(STATE_DIR)));
assert_eq!(crate::run_mode::declared_identity(), None);
}
return;
}
let tmp = std::env::temp_dir().join(format!("benilla-launcher-{}", std::process::id()));
std::fs::remove_dir_all(&tmp).ok();
std::fs::create_dir_all(&tmp).unwrap();
std::fs::write(
tmp.join(".probe-identity"),
"WOW_USER=probe9\nWOW_PASS=unused\nWOW_CHAR=Modchar\n",
)
.unwrap();
let name =
"local_state::tests::a_recorded_launcher_folder_holds_the_state_and_the_identity";
let out = std::process::Command::new(std::env::current_exe().unwrap())
.args(["--exact", name, "--nocapture"])
.env(CHILD, &tmp)
.env_remove("BENILLA_HOME")
.env_remove("WOW_CAPTURE")
.output()
.unwrap();
let report = format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
assert!(
out.status.success() && report.contains("1 passed"),
"the child run:\n{report}"
);
std::fs::remove_dir_all(&tmp).ok();
}
#[test]
fn write_atomic_creates_dirs_and_replaces_whole_files() {
let _l = ENV_LOCK
+6 -14
View File
@@ -127,9 +127,9 @@ pub(crate) fn free_fly_hint() -> String {
/// This crate's source directory on a dev build, `None` in a player build. The `cfg` must be real,
/// not a runtime check: `env!("CARGO_MANIFEST_DIR")` names the build machine, and the literal must
/// be absent from a player binary. Read by [`crate::ui_script`] (live `assets/ui`) and
/// [`crate::local_state`] (the project-folder home); held by
/// [`tests::the_dev_plane_has_exactly_one_door`].
/// be absent from a player binary. Read by [`crate::ui_script`] for the live `assets/ui`, which are
/// benilla's own files whoever the launcher is; the launcher's folder is
/// [`benilla_formats::project_folder`]. Held by [`tests::the_dev_plane_has_exactly_one_door`].
pub(crate) fn dev_source_dir() -> Option<&'static std::path::Path> {
#[cfg(feature = "dev")]
{
@@ -158,23 +158,15 @@ pub(crate) struct DeclaredIdentity {
pub(crate) character: String,
}
/// The declaration, read off the project folder; a player build has none.
#[cfg(feature = "dev")]
/// The declaration, read off the launcher's project folder ([`benilla_formats::project_folder`]);
/// a player build has none.
pub(crate) fn declared_identity() -> Option<DeclaredIdentity> {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(2)?;
let root = benilla_formats::project_folder()?;
parse_identity(&std::fs::read_to_string(root.join(".probe-identity")).ok()?)
}
#[cfg(not(feature = "dev"))]
pub(crate) fn declared_identity() -> Option<DeclaredIdentity> {
None
}
/// The file as an identity, `None` unless both the account and the character are there; the
/// password is never read.
#[cfg_attr(not(feature = "dev"), allow(dead_code))]
fn parse_identity(text: &str) -> Option<DeclaredIdentity> {
let field = |key: &str| {
text.lines()
+65 -1
View File
@@ -6,7 +6,7 @@
use std::path::Path;
use std::process::Command;
/// Stamp the commit this binary was built from into the calling package as five `rustc-env`
/// 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`:
///
@@ -17,6 +17,9 @@ use std::process::Command;
/// (`v0.2.0-12-gb17be27`), which says how far past its release the commit is.
/// - `BENILLA_PROFILE`: the profile directory's name, which tells `ship` from `release` where
/// cargo's `PROFILE` does not.
/// - `BENILLA_PROJECT_DIR`: the folder a dev build keeps its `WoW` link, `benilla-config/` and
/// `.probe-identity` in, the calling package's workspace root ([`workspace_root`]); empty unless
/// the package's own `dev` feature is on, so a player binary carries no source path.
///
/// The git vars are empty when git cannot answer (no `.git`, no `git` on `PATH`, no tag in
/// reach), which the runtime reports as an unknown commit or the bare version. The rerun
@@ -84,4 +87,65 @@ pub fn emit() {
.map(str::to_owned)
.unwrap_or_else(|| std::env::var("PROFILE").unwrap_or_default());
println!("cargo::rustc-env=BENILLA_PROFILE={profile}");
let project = if std::env::var_os("CARGO_FEATURE_DEV").is_some() {
workspace_root(Path::new(&dir)).display().to_string()
} else {
String::new()
};
println!("cargo::rustc-env=BENILLA_PROJECT_DIR={project}");
}
/// The workspace root of the package at `manifest_dir`: the nearest folder up from it holding
/// `Cargo.lock`, which cargo writes at the root before any build script runs, else the package's
/// own folder. For a launcher inside benilla's checkout that is the checkout.
fn workspace_root(manifest_dir: &Path) -> &Path {
manifest_dir
.ancestors()
.find(|d| d.join("Cargo.lock").is_file())
.unwrap_or(manifest_dir)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_workspace_root_is_the_nearest_folder_holding_the_lockfile() {
let tmp = std::env::temp_dir().join(format!("benilla-stamp-{}", std::process::id()));
let member = tmp.join("ws/crates/launcher");
std::fs::create_dir_all(&member).unwrap();
// A lockfile further up belongs to some other tree and must not win over the nearer one.
std::fs::write(tmp.join("Cargo.lock"), "").unwrap();
std::fs::write(tmp.join("ws/Cargo.lock"), "").unwrap();
assert_eq!(workspace_root(&member), tmp.join("ws"));
// A package that is its own workspace keeps its lockfile beside its manifest.
std::fs::write(member.join("Cargo.lock"), "").unwrap();
assert_eq!(workspace_root(&member), member);
std::fs::remove_dir_all(&tmp).ok();
let bare = std::env::temp_dir().join(format!("benilla-stamp-bare-{}", std::process::id()));
std::fs::create_dir_all(&bare).unwrap();
let root = workspace_root(&bare);
assert!(
root == bare || root.join("Cargo.lock").is_file(),
"no lockfile anywhere up means the package's own folder: {}",
root.display()
);
std::fs::remove_dir_all(&bare).ok();
}
/// benilla's own launchers stamp the checkout, the folder a dev build always used.
#[test]
fn a_launcher_in_this_checkout_stamps_the_checkout() {
let shim = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("benilla");
let checkout = Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(2)
.unwrap();
assert_eq!(workspace_root(&shim), checkout);
}
}
+2 -2
View File
@@ -9,8 +9,8 @@ description = "WoW 1.12.1 asset-format adapters (MPQ/BLP/DBC/ADT/WDT) and the be
workspace = true
[features]
# `dev` gates one thing, `install::candidates`' project-folder probe, which bakes this source
# tree's path into the binary. Every dependent takes this crate without default features and
# `dev` gates one thing, the project folder (`install::project_folder`), whose fallback bakes this
# source tree's path into the binary. Every dependent takes this crate without default features and
# forwards `dev`, or unification puts the path back into a player build.
default = ["dev"]
dev = []
+85 -14
View File
@@ -3,7 +3,7 @@
//!
//! 1. `$WOW_DATA`. Set and empty (`WOW_DATA=`) means no install and ends the search, so the
//! no-install path runs on a machine that has one.
//! 2. `<project folder>/WoW/Data`, `dev` builds only, from this crate's `CARGO_MANIFEST_DIR`.
//! 2. `<project folder>/WoW/Data`, `dev` builds only ([`project_folder`]).
//! 3. `<exe dir>/Data`, then `<exe dir>/WoW/Data`, the release layouts.
//!
//! `dev` is a default feature here, so every dependent declares `default-features = false` and
@@ -12,6 +12,44 @@
//! catch it.
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
/// The launcher's project folder, handed in by its entry point ([`set_project_folder`]).
static LAUNCHER_FOLDER: OnceLock<PathBuf> = OnceLock::new();
/// Record the launcher's project folder, as its build stamp names it (`BuildId::project_dir`); an
/// empty stamp, a player build's, records nothing. The entry point calls this once, before
/// anything resolves from the folder, and a later call changes nothing.
pub fn set_project_folder(dir: &str) {
if !dir.is_empty() {
let _ = LAUNCHER_FOLDER.set(PathBuf::from(dir));
}
}
/// The folder a dev build keeps its `WoW` link, `benilla-config/` and `.probe-identity` in: the
/// launcher's, when its entry point recorded one, else benilla's own checkout, so a crate built on
/// top of benilla resolves from its folder and not from cargo's checkout of benilla. `None` in a
/// player build, which looks in no source tree.
pub fn project_folder() -> Option<&'static Path> {
#[cfg(feature = "dev")]
{
LAUNCHER_FOLDER
.get()
.map(PathBuf::as_path)
.or_else(benilla_checkout)
}
#[cfg(not(feature = "dev"))]
{
None
}
}
/// benilla's own checkout: this crate's manifest dir, two levels up. The addon corpus always
/// resolves here, since its tests are benilla's.
#[cfg(feature = "dev")]
fn benilla_checkout() -> Option<&'static Path> {
Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(2)
}
/// The vanilla `Data` directory, or `None` when there is no install. Uncached, so a `$WOW_DATA`
/// change is seen.
@@ -23,6 +61,7 @@ pub fn wow_data() -> Option<PathBuf> {
pub fn candidates() -> Vec<PathBuf> {
candidates_from(
std::env::var_os("WOW_DATA").map(PathBuf::from),
project_folder(),
std::env::current_exe()
.ok()
.and_then(|e| e.parent().map(Path::to_path_buf)),
@@ -31,7 +70,11 @@ pub fn candidates() -> Vec<PathBuf> {
/// [`candidates`] with the environment passed in: a test that set `$WOW_DATA` would change the
/// answer for every test running beside it. `tests/wow_data_env.rs` covers the real read.
fn candidates_from(override_dir: Option<PathBuf>, exe_dir: Option<PathBuf>) -> Vec<PathBuf> {
fn candidates_from(
override_dir: Option<PathBuf>,
project: Option<&Path>,
exe_dir: Option<PathBuf>,
) -> Vec<PathBuf> {
let mut out = Vec::with_capacity(4);
// 1. The override; set and empty is no install at all, even in a dev build
@@ -43,10 +86,8 @@ fn candidates_from(override_dir: Option<PathBuf>, exe_dir: Option<PathBuf>) -> V
out.push(over);
}
// 2. The project folder, dev builds only: this crate's manifest dir, two levels up, is the
// workspace root for every caller.
#[cfg(feature = "dev")]
if let Some(root) = Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(2) {
// 2. The project folder, dev builds only.
if let Some(root) = project {
out.push(root.join("WoW/Data"));
}
@@ -113,8 +154,9 @@ fn skipped_under(required: bool, what: &str, looked_in: &[PathBuf]) {
}
/// The third-party addon corpus the UI engine's real-addon tests run against, or `None`:
/// `$BENILLA_ADDON_CORPUS`, then `<project folder>/wow-addons-vanilla` (`dev` only), a gitignored
/// link beside `WoW`. Never a folder beside the checkout, which depends on where the checkout sits.
/// `$BENILLA_ADDON_CORPUS`, then `<benilla's checkout>/wow-addons-vanilla` (`dev` only), a
/// gitignored link beside `WoW`. Never a folder beside the checkout, which depends on where the
/// checkout sits.
pub fn addon_corpus() -> Option<PathBuf> {
addon_corpus_candidates().into_iter().find(|c| c.is_dir())
}
@@ -130,9 +172,9 @@ fn addon_corpus_candidates_from(override_dir: Option<PathBuf>) -> Vec<PathBuf> {
if let Some(over) = override_dir {
out.push(over);
}
// The project folder, dev builds only, as the install's rung 2.
// benilla's checkout, dev builds only, whoever the launcher is: the corpus tests are benilla's.
#[cfg(feature = "dev")]
if let Some(root) = Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(2) {
if let Some(root) = benilla_checkout() {
out.push(root.join("wow-addons-vanilla"));
}
out
@@ -162,9 +204,14 @@ macro_rules! addon_corpus_or_skip {
mod tests {
use super::*;
/// No test here touches the process environment (see [`candidates_from`]).
/// No test here touches the process environment (see [`candidates_from`]), or records a
/// launcher's folder, which is process-wide (`tests/project_folder.rs` has a process of its own).
fn probe(over: Option<&str>, exe: Option<&str>) -> Vec<PathBuf> {
candidates_from(over.map(PathBuf::from), exe.map(PathBuf::from))
candidates_from(
over.map(PathBuf::from),
project_folder(),
exe.map(PathBuf::from),
)
}
#[test]
@@ -195,7 +242,7 @@ mod tests {
// The winner depends on the build (dev puts the project folder first), so assert only
// that it is not the ghost and exists.
let chosen = candidates_from(Some(ghost.clone()), Some(tmp.clone()))
let chosen = candidates_from(Some(ghost.clone()), project_folder(), Some(tmp.clone()))
.into_iter()
.find(|c| c.is_dir());
assert_ne!(
@@ -224,6 +271,11 @@ mod tests {
#[cfg(not(feature = "dev"))]
#[test]
fn a_player_build_carries_no_source_tree_path() {
assert_eq!(
project_folder(),
None,
"a player build has no project folder, whatever its launcher stamped"
);
let root = env!("CARGO_MANIFEST_DIR");
for c in probe(None, Some("/games/benilla")) {
assert!(
@@ -234,6 +286,8 @@ mod tests {
}
}
/// With no launcher folder recorded (a test, an example, a tool) the rung is benilla's own
/// checkout; with one, it is that folder's, and benilla's checkout is not looked in.
#[cfg(feature = "dev")]
#[test]
fn a_dev_build_looks_in_the_project_folder() {
@@ -241,17 +295,34 @@ mod tests {
.ancestors()
.nth(2)
.unwrap();
assert_eq!(
project_folder(),
Some(root),
"nothing recorded: the checkout"
);
let c = probe(None, Some("/games/benilla"));
assert!(
c.contains(&root.join("WoW/Data")),
"the dev build lost its project-folder candidate ({}): {c:?}",
root.display()
);
let launcher = Path::new("/home/player/hello-mod");
let c = candidates_from(None, Some(launcher), Some(PathBuf::from("/games/benilla")));
assert_eq!(
c,
vec![
launcher.join("WoW/Data"),
PathBuf::from("/games/benilla/Data"),
PathBuf::from("/games/benilla/WoW/Data"),
],
"a launcher's folder takes the rung, and nothing else looks in benilla's checkout"
);
}
#[cfg(feature = "dev")]
#[test]
fn the_corpus_ladder_is_override_then_the_project_folder_and_nothing_outside_it() {
fn the_corpus_ladder_is_override_then_benillas_checkout_and_nothing_outside_it() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(2)
+4 -1
View File
@@ -18,7 +18,10 @@ mod tga;
pub use tga::tga_to_rgba;
/// Where the WoW install is; [`Chain`] opens it.
mod install;
pub use install::{addon_corpus, addon_corpus_candidates, candidates, skipped, wow_data};
pub use install::{
addon_corpus, addon_corpus_candidates, candidates, project_folder, set_project_folder, skipped,
wow_data,
};
mod characters;
pub use characters::{
equip_blits, equip_column, equip_region_candidates, equip_tex_dir, equip_tile, forearm_dressed,
@@ -0,0 +1,45 @@
//! A launcher's recorded project folder is where a dev build looks for the install, in place of
//! benilla's checkout, while the addon corpus stays benilla's. Recording is process-wide and every
//! test resolves through it, so this one test has a file, and so a process, of its own.
use std::path::Path;
#[test]
fn a_recorded_launcher_folder_is_where_a_dev_build_looks() {
let tmp = std::env::temp_dir().join(format!("benilla-project-{}", std::process::id()));
std::fs::create_dir_all(&tmp).unwrap();
// This process's own environment: `WOW_DATA=` would empty the ladder, and an override would
// move the corpus.
std::env::remove_var("WOW_DATA");
std::env::remove_var("BENILLA_ADDON_CORPUS");
let checkout = Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(2)
.unwrap();
// A player build's stamp is empty and records nothing.
benilla_formats::set_project_folder("");
if cfg!(feature = "dev") {
assert_eq!(benilla_formats::project_folder(), Some(checkout));
}
benilla_formats::set_project_folder(tmp.to_str().unwrap());
benilla_formats::set_project_folder("/a/later/call/changes/nothing");
if cfg!(feature = "dev") {
assert_eq!(benilla_formats::project_folder(), Some(tmp.as_path()));
let c = benilla_formats::candidates();
assert_eq!(c.first(), Some(&tmp.join("WoW/Data")), "{c:?}");
assert!(
!c.contains(&checkout.join("WoW/Data")),
"benilla's checkout is still looked in for the install: {c:?}"
);
assert_eq!(
benilla_formats::addon_corpus_candidates(),
vec![checkout.join("wow-addons-vanilla")],
"the corpus is benilla's whoever the launcher is"
);
} else {
assert_eq!(benilla_formats::project_folder(), None);
}
std::fs::remove_dir_all(&tmp).ok();
}
+7 -1
View File
@@ -1,7 +1,9 @@
//! Which build is running: the release and the commit the binary was built from, stamped at
//! compile time by the launcher shims' build script (`benilla-buildstamp`) and passed to `run` as
//! the [`BuildId`] resource, and whether a crate on top of benilla extended it. It shows in the
//! startup log line ([`banner`]), in crash reports and in the debug panel's footer.
//! startup log line ([`banner`]), in crash reports and in the debug panel's footer. A dev build's
//! stamp also names the launcher's project folder, which the entry point hands to
//! `benilla_formats::set_project_folder`.
use bevy::prelude::*;
@@ -22,6 +24,9 @@ pub struct BuildId {
pub date: &'static str,
/// The cargo profile directory: `debug`, `release` or `ship`.
pub profile: &'static str,
/// The launcher's project folder in a dev build, where its `WoW` link, `benilla-config/` and
/// `.probe-identity` live; empty in a player build, which carries no source path.
pub project_dir: &'static str,
/// Whether a crate on top of benilla added plugins through `benilla_app::run_with`. The entry
/// point sets it, so a launcher leaves it to `..Default::default()`.
pub extended: bool,
@@ -91,6 +96,7 @@ mod tests {
short,
date: "2026-09-27",
profile: "release",
project_dir: "",
extended: false,
}
}
+3
View File
@@ -52,6 +52,9 @@ const FOVY: f32 = 1.221_730_5;
/// Builds and runs the world viewer; `build` is the launcher shim's compile-time git stamp.
pub fn run(build: BuildId) -> AppExit {
// The install resolves from the launcher's folder when its stamp names one, as the client's
// does; this shim has no `dev` feature, so it stamps none and the viewer keeps benilla's.
benilla_formats::set_project_folder(build.project_dir);
let mut app = App::new();
app.insert_resource(build);
+1
View File
@@ -12,6 +12,7 @@ fn main() -> benilla_world::AppExit {
short: env!("BENILLA_GIT_SHORT"),
date: env!("BENILLA_GIT_DATE"),
profile: env!("BENILLA_PROFILE"),
project_dir: env!("BENILLA_PROJECT_DIR"),
..Default::default()
})
}
+1
View File
@@ -11,6 +11,7 @@ fn main() -> benilla_app::AppExit {
short: env!("BENILLA_GIT_SHORT"),
date: env!("BENILLA_GIT_DATE"),
profile: env!("BENILLA_PROFILE"),
project_dir: env!("BENILLA_PROJECT_DIR"),
..Default::default()
})
}
+1 -1
View File
@@ -212,7 +212,7 @@
- `WOW_CULL_TRACE` — benilla-world/src/exterior_cull.rs
- `WOW_CULLDUMP` — benilla-world/src/wmo_portal/mod.rs
- `WOW_CURSOR_TRACE` — benilla-app/src/cursor.rs
- `WOW_DATA` — benilla-app/src/local_state.rs, benilla-formats/src/install.rs, benilla-formats/tests/wow_data_env.rs, benilla-formats/tests/wow_data_none.rs
- `WOW_DATA` — benilla-app/src/local_state.rs, benilla-formats/src/install.rs, benilla-formats/tests/project_folder.rs, benilla-formats/tests/wow_data_env.rs, benilla-formats/tests/wow_data_none.rs
- `WOW_DEMO_BOTTOM_BARS` — benilla-app/src/ui_script/mod.rs
- `WOW_DEPTH` — benilla-app/src/capture/depth_probe.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
- `WOW_DEPTH_AFTER` — benilla-app/src/capture/depth_probe.rs