Nobody working on DarkRoom has a Mac, so every macOS build is in the hands of someone who can send a log and cannot attach a debugger. Three changes make that log worth sending: - The desktop's default filter on macOS is `debug` for every `dr_*` crate, the desktop crate and `onnxruntime` (the runtime's own session log). - The state directory — the log and crash records — is `~/Library/Logs` on macOS rather than the `~/.local/state` Finder hides; Console.app lists it. Config and data keep the Unix rules. - A `diagnostic` cargo profile: release plus line tables, so a crash record's backtrace reads file:line. On macOS the tables are in the `.dSYM` beside the executable, which the bundle must keep.
129 lines
6.0 KiB
Rust
129 lines
6.0 KiB
Rust
//! DarkRoom desktop entry point.
|
|
//!
|
|
//! darkroom-desktop <file-or-directory>...
|
|
//! darkroom-desktop --version
|
|
|
|
// TRACES: FR-PLAT-WIN-2
|
|
// A GUI-subsystem executable, or Windows opens a console window behind the
|
|
// application for the life of the process. Release only: the console is where
|
|
// the log goes when there is no file, and a debug build is run from one.
|
|
// `--version` still prints under this — stdout is simply not attached when
|
|
// launched from Explorer, which is not where anyone asks for a version.
|
|
#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use dr_plat::diagnostics::Installed;
|
|
|
|
/// What the log keeps when `RUST_LOG` does not say.
|
|
#[cfg(not(target_os = "macos"))]
|
|
const DEFAULT_LOG: &str =
|
|
"info,wgpu_core=warn,wgpu_hal=warn,zbus=warn,tracing=warn,calloop=warn,rawler=warn";
|
|
|
|
/// The same, and `debug` from this application's own crates and from ONNX
|
|
/// Runtime, whose `debug` is how many nodes each provider took
|
|
/// (docs/dev/macos.md). Nobody here runs a Mac: every macOS build is in
|
|
/// the hands of someone who can send us a log and cannot attach a debugger,
|
|
/// so the log is written as if for a debug build. `dr_` is a prefix, and
|
|
/// `env_logger` matches directives by prefix, so it names every `dr-*`
|
|
/// crate — present and future — without naming a dependency.
|
|
#[cfg(target_os = "macos")]
|
|
const DEFAULT_LOG: &str = "info,dr_=debug,darkroom_desktop=debug,onnxruntime=debug,\
|
|
wgpu_core=warn,wgpu_hal=warn,zbus=warn,tracing=warn,calloop=warn,rawler=warn";
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
// TRACES: FR-PLAT-WIN-3
|
|
// Before the logger, the crash hook and everything else: this exists so a
|
|
// build made on a machine that cannot run the application — the Linux CI
|
|
// producing the Windows binary, checked under Wine — has an exit that
|
|
// proves the executable starts without opening a window or touching the
|
|
// user's directories (docs/dev/windows.md §6).
|
|
if std::env::args().nth(1).as_deref() == Some("--version") {
|
|
println!("darkroom-desktop {}", env!("CARGO_PKG_VERSION"));
|
|
return Ok(());
|
|
}
|
|
|
|
// Built rather than `init`ed, so the same logger can be handed to the
|
|
// diagnostics tee: `env_logger` keeps writing to stderr exactly as before,
|
|
// and every record it accepts is also appended to the on-disk log
|
|
// (NFR-OPS-1). `filter()` is asked afterwards because the environment may
|
|
// have overridden the default below, and the file must not be quieter than
|
|
// the terminal.
|
|
let console =
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(DEFAULT_LOG))
|
|
.build();
|
|
let level = console.filter();
|
|
let logging = dr_plat::diagnostics::install(Box::new(console), level);
|
|
|
|
// Immediately after the logger and before anything that could fail. Until
|
|
// now a panic on desktop went to stderr and died with the terminal, which
|
|
// means every panic a user has ever hit was unreportable: the process
|
|
// survives (the panicking worker does not), a control goes dead, and there
|
|
// is nothing on disk to say why. The record is local and stays local —
|
|
// there is no upload path, by design; see `dr_plat::crash`.
|
|
dr_plat::crash::install(env!("CARGO_PKG_VERSION"));
|
|
|
|
log::info!("DarkRoom v{}", env!("CARGO_PKG_VERSION"));
|
|
// First thing in the file, so a user asked for "the log" can find it
|
|
// without being told a path over the phone.
|
|
match &logging {
|
|
Installed::ToFile(path) => log::info!("logging to {}", path.display()),
|
|
Installed::ConsoleOnly(why) => log::warn!("no log file this session: {why}"),
|
|
}
|
|
|
|
let paths: Vec<PathBuf> = std::env::args().skip(1).map(PathBuf::from).collect();
|
|
if paths.is_empty() {
|
|
eprintln!("usage: darkroom-desktop <file-or-directory>...");
|
|
}
|
|
|
|
// Before the window: the probe runs on its own thread and the first
|
|
// frame does not wait for it, but the models a background job asks for
|
|
// should already know where the runtime is (docs/dev/inference.md §4).
|
|
dr_ui::inference::init(runtime_dirs());
|
|
|
|
dr_ui::run(paths)?;
|
|
|
|
// Skip Rust's normal static/thread-local teardown on the way out: a
|
|
// background zbus/keyring connection opened by dr_ui::launch_ui can
|
|
// still be alive here, and unwinding through it races its async-io
|
|
// reactor thread, panicking with "thread local ... during or after
|
|
// destruction" when the window is closed.
|
|
std::process::exit(0);
|
|
}
|
|
|
|
/// Where a desktop package may have put `libonnxruntime`, most specific
|
|
/// first. None of these existing is the tract build, which is a complete
|
|
/// application and not an error (docs/dev/inference.md §3).
|
|
///
|
|
/// `DARKROOM_ORT_DIR` is for a developer pointing at a runtime that is not
|
|
/// installed — the wheel's `capi` directory, say. Then beside the executable
|
|
/// and in the package's private library directory, for a package that
|
|
/// bundles its own; then the user's own `runtime/` beside the models, where
|
|
/// `tools/fetch-desktop-runtime.sh` puts one; then the Flatpak prefix; then
|
|
/// the system library directory, for a distribution that ships ONNX Runtime
|
|
/// as a package of its own. The user's copy outranks the system's because
|
|
/// the system's is the one most likely to be built without the GPU
|
|
/// providers, or against the wrong cuDNN — and a system copy whose providers
|
|
/// do not load is not a problem, only a slower app: the probe builds a real
|
|
/// session before believing a provider.
|
|
fn runtime_dirs() -> Vec<PathBuf> {
|
|
let mut dirs = Vec::new();
|
|
if let Some(dir) = std::env::var_os("DARKROOM_ORT_DIR") {
|
|
dirs.push(PathBuf::from(dir));
|
|
}
|
|
if let Ok(exe) = std::env::current_exe() {
|
|
if let Some(bin) = exe.parent() {
|
|
dirs.push(bin.to_path_buf());
|
|
dirs.push(bin.join("../lib/darkroom"));
|
|
}
|
|
}
|
|
dirs.push(dr_ui::inference::user_runtime_dir());
|
|
#[cfg(target_os = "linux")]
|
|
dirs.extend([
|
|
PathBuf::from("/app/lib/darkroom"),
|
|
PathBuf::from("/usr/lib/darkroom"),
|
|
PathBuf::from("/usr/lib"),
|
|
]);
|
|
dirs
|
|
}
|