lighty-java 26.9.3

Java runtime management for Lighty Launcher
Documentation
# Runtime — spawn a `java` process

`JavaRuntime` is a thin wrapper around `tokio::process::Command`. It
spawns the binary and returns the `Child` with stdout / stderr piped,
leaving the caller free to stream them however it wants.

## API

```rust
pub struct JavaRuntime(pub PathBuf);

impl JavaRuntime {
    pub fn new(path: PathBuf) -> Self;

    pub async fn execute(
        &self,
        arguments: Vec<String>,
        game_dir: &Path,
    ) -> JavaRuntimeResult<tokio::process::Child>;
}
```

`game_dir` becomes the process's working directory (this is what gets
passed to Minecraft via `${game_directory}`).

Both pipes are **always** created, so whoever owns the `Child` has to
drain them: an unread pipe fills up and the JVM blocks on `write` once
the buffer is full. `lighty-launch` does this in
`handle_console_streams`.

Windows-only detail: `execute` sets `CREATE_NO_WINDOW` so spawned
processes don't pop up a console window.

## Examples

### Print `java -version`

```rust
use lighty_java::runtime::JavaRuntime;
use tokio::io::{AsyncBufReadExt, BufReader};
use std::path::Path;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let rt = JavaRuntime::new("/usr/bin/java".into());
    let mut child = rt.execute(vec!["-version".into()], Path::new(".")).await?;

    let stderr = child.stderr.take().expect("stderr is piped");
    let mut lines = BufReader::new(stderr).lines();
    while let Some(line) = lines.next_line().await? {
        eprintln!("{line}");
    }
    Ok(())
}
```

`java -version` writes to stderr — drain stdout too, or the pipe fills.

### Launch a JAR with memory tuning

```rust
use lighty_java::runtime::JavaRuntime;
use tokio::io::{self, AsyncBufReadExt, BufReader};
use std::path::Path;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let rt = JavaRuntime::new("/path/to/java".into());

    let args = vec![
        "-Xmx4G".into(),
        "-Xms1G".into(),
        "-XX:+UseG1GC".into(),
        "-jar".into(),
        "minecraft.jar".into(),
    ];
    let mut child = rt.execute(args, Path::new("/games/minecraft")).await?;

    let stdout = child.stdout.take().expect("stdout is piped");
    let mut stderr = child.stderr.take().expect("stderr is piped");
    tokio::spawn(async move { let _ = io::copy(&mut stderr, &mut io::sink()).await; });

    let mut lines = BufReader::new(stdout).lines();
    while let Some(line) = lines.next_line().await? {
        println!("{line}");
    }

    // Call `child.kill().await` from elsewhere to bail out early
    Ok(())
}
```

### Wait for the exit code

Drain both pipes first, then `child.wait().await?` yields the exit
code:

```rust
# use lighty_java::runtime::JavaRuntime;
# use tokio::io::{self, AsyncReadExt};
# use std::path::Path;
# async fn run() -> anyhow::Result<()> {
let rt = JavaRuntime::new("/usr/bin/java".into());
let mut child = rt.execute(vec!["-version".into()], Path::new(".")).await?;
let mut stdout = child.stdout.take().expect("stdout is piped");
let mut stderr = child.stderr.take().expect("stderr is piped");
io::copy(&mut stdout, &mut io::sink()).await?;
io::copy(&mut stderr, &mut io::sink()).await?;

let status = child.wait().await?;
println!("exit code: {:?}", status.code());
# Ok(()) }
```

## Errors

```rust
pub enum JavaRuntimeError {
    NotFound { path: PathBuf },
    NonZeroExit { code: i32 },
    IoCaptureFailure,                    // stdout/stderr couldn't be captured
    Spawn(std::io::Error),
    SignalTerminated,
}
```

## How `lighty-launch` uses it

`lighty-launch::launch::runner` calls `execute` with the full launch
argv (built from `Arguments`) and hands the `Child` to
`handle_console_streams` — every line becomes a `ConsoleOutputEvent`
and the final exit triggers `InstanceExited`. Without the `events`
feature the pipes go to a sink so the JVM never blocks.

## See also

- [`overview.md`]./overview.md — crate scope
- [`installation.md`]./installation.md — get a binary path first
- [`../../launch/docs/launch.md`]../../launch/docs/launch.md — how
  `Launcher` builds the argv
- [`../../launch/docs/arguments.md`]../../launch/docs/arguments.md  JVM arg construction