[package]
name = "processkit"
version = "0.7.1"
edition = "2024"
description = "Child-process management: kill-on-drop process trees and async run-and-capture"
license = "MIT"
repository = "https://github.com/ZelAnton/ProcessKit-rs"
readme = "README.md"
keywords = ["process", "subprocess", "job-object", "cgroup", "tokio"]
categories = ["os", "asynchronous", "command-line-utilities"]
# Keep the published crate lean: ship code, docs, and license — not the
# dev/CI/agent tooling that only matters in the repo.
exclude = [
  ".claude/",
  ".github/",
  "ideas/",
  ".editorconfig",
  ".gitattributes",
  "AGENTS.md",
  "CLAUDE.md",
  "cliff.toml",
  "deny.toml",
  "rust-toolchain.toml",
]
# Minimum Supported Rust Version. 1.88 is required for `let`-chains
# (`if let … && let …`), used in the unix process-group/cgroup code. The `msrv`
# CI job verifies it — keep the two in sync; raise both when adopting a newer feature.
rust-version = "1.88"

# Build docs.rs with every feature so the optional surfaces — `limits`
# (ResourceLimits), the `mock` MockRunner, the `tracing` integration — are
# documented (default features alone hide them).
[package.metadata.docs.rs]
all-features = true

# Every public type must be `Debug` (consumers derive Debug on structs holding
# ours); warn-level here becomes a hard error under CI's `-D warnings`.
[lints.rust]
missing_debug_implementations = "warn"

# ---------------------------------------------------------------------------
# Dependencies — every entry carries a "why" (see AGENTS.md "Dependency
# management"). Pin major versions, enable only the features used, keep
# Cargo.lock committed.
# ---------------------------------------------------------------------------
[dependencies]
# Async runtime + child-process management: `process` spawns children,
# `time` enforces timeouts, `io-util` streams stdin/stdout line-by-line,
# `rt` powers the background stderr-drain task, `macros` for `#[tokio::main]`
# in examples, `sync` for the oneshot/Notify used in shutdown coordination,
# `fs` for the async file handle behind `Stdin::from_file`,
# `net` for the TcpStream the `wait_for_port` readiness probe connects with.
tokio = { version = "1", features = ["process", "time", "io-util", "rt", "macros", "sync", "fs", "net"] }
# `async fn` in the object-safe `ProcessRunner` trait so it stays `dyn`-able
# and mockable (native async-in-trait isn't object-safe on our MSRV).
async-trait = "0.1"
# Structured `Error` carrying program / exit code / stderr / timeout instead
# of stringly-typed failures; `#[non_exhaustive]` so variants can grow.
thiserror = "2"
# Decode child stdout/stderr in non-UTF-8 legacy encodings (Shift-JIS,
# Windows-1252, GBK, …) for the per-stream encoding-override option; default
# stays UTF-8.
encoding_rs = "0.8"
# Adapt tokio's `Lines` reader into an `impl Stream` for the streaming-output
# helper, so callers consume stdout with the standard `Stream` combinators.
# `io-util` gates the `wrappers::LinesStream` adapter we use.
tokio-stream = { version = "0.1", features = ["io-util"] }
# Optional per-run observability (program/exit code/duration — deliberately
# NOT argv or env, which routinely carry secrets). Off by default; zero cost
# unless the `tracing` feature is enabled.
tracing = { version = "0.1", optional = true }
# Optional auto-generated `MockRunner` for downstream tests. Test-only; pulled
# in solely behind the `mock` feature, never in production builds.
mockall = { version = "0.13", optional = true }
# Optional `CancellationToken` for the `cancellation` feature: structured-
# concurrency cancellation that tears a run's tree down. The `sync` module
# (where CancellationToken lives) is always compiled in tokio-util — no inner
# feature needed; default-features = false pulls in nothing else.
tokio-util = { version = "0.7", default-features = false, optional = true }
# Optional serialization for the `record` feature's JSON cassettes
# (RecordReplayRunner): `derive` for the cassette structs, serde_json for the
# human-diffable on-disk format. Never in production builds without `record`.
serde = { version = "1", features = ["derive"], optional = true }
serde_json = { version = "1", optional = true }

[features]
# Both default features are *visibility* gates: additive, hiding API without
# changing the core's semantics — the kill-on-drop tree guarantee is
# unconditional in every configuration. (A broader visibility split was tried
# and deliberately rolled back: see ideas/three-layer-resource-split.md.)
default = ["stats", "process-control"]
# Resource measurement: `ProcessGroupStats`, `ProcessGroup::stats`, and the
# per-process `RunningProcess::cpu_time`/`peak_memory_bytes` diagnostics.
# Compile it out (`--no-default-features`) to drop the accounting code and, on
# Windows, the ProcessStatus FFI used only for the peak-memory readout.
stats = ["windows-sys/Win32_System_ProcessStatus"]
# Whole-tree resource caps: `ResourceLimits`, the `memory_max`/`max_processes`/
# `cpu_quota` builders on `ProcessGroupOptions`, and `Error::ResourceLimit`.
# Implies `stats` as policy — resource read and write travel together toward the
# possible `processkit-resource` split (see ideas/) — not as a code dependency.
limits = ["stats"]
# Tree control beyond contain+kill: `Signal` and
# `ProcessGroup::{signal, suspend, resume, members, adopt}`. Gated because it
# matches the layer a future `processkit-sys` split would carve out.
process-control = []
# Expose the `mockall`-generated `MockRunner` for consumers' tests.
mock = ["dep:mockall"]
# Emit a `tracing` event per command run (program, exit code, duration —
# argv/env are deliberately not logged: they routinely carry secrets).
tracing = ["dep:tracing"]
# First-class run cancellation: `Command::cancel_on(token)`, `Error::Cancelled`,
# and the `CancellationToken` re-export. Off by default; pulls in `tokio-util`.
cancellation = ["dep:tokio-util"]
# Record/replay cassettes over the `ProcessRunner` seam: `RecordReplayRunner`
# records real `Invocation → ProcessResult` pairs to a JSON fixture and replays
# them hermetically. Off by default; pulls in serde + serde_json.
record = ["dep:serde", "dep:serde_json"]

[target.'cfg(windows)'.dependencies]
# Win32 Job Object FFI for kill-on-close process trees: CreateJobObjectW /
# SetInformationJobObject / AssignProcessToJobObject / TerminateJobObject plus
# the HANDLE type & CloseHandle. Kept on the same major as tokio/mio's copy so
# the lockfile carries a single windows-sys.
windows-sys = { version = "0.61", features = [
  "Win32_Foundation",
  "Win32_System_JobObjects",
  # JOBOBJECT_EXTENDED_LIMIT_INFORMATION + GetProcessTimes (per-process CPU);
  # also CREATE_SUSPENDED / OpenThread / ResumeThread for race-free containment
  # (spawn suspended → assign to job → resume the primary thread).
  "Win32_System_Threading",
  # CreateJobObjectW's signature references SECURITY_ATTRIBUTES.
  "Win32_Security",
  # Win32_System_ProcessStatus (K32GetProcessMemoryInfo, peak working set) is
  # enabled by the `stats` feature above — it serves only the metrics readout.
  # Thread snapshot (CreateToolhelp32Snapshot / Thread32First/Next) to find a
  # suspended child's primary thread for ResumeThread — std/tokio expose only
  # the process handle, not the PROCESS_INFORMATION thread handle.
  "Win32_System_Diagnostics_ToolHelp",
] }

[target.'cfg(unix)'.dependencies]
# Raw syscalls std can't express: the POSIX process-group backend (setpgid /
# killpg / kill for teardown and liveness probes) on every unix, plus — on Linux
# — joining cgroup.procs in pre_exec (async-signal-safe getpid/open/write/close).
libc = "0.2"

[dev-dependencies]
# `#[tokio::test]` plus a multi-threaded runtime to drive the async tests;
# `test-util` provides the paused clock for the supervisor's backoff-timing
# tests (virtual time instead of real sleeps); `net` binds the ephemeral
# TcpListener the `wait_for_port` integration test probes against.
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "io-util", "test-util", "net"] }
# Auto-cleaned, parallel-safe temp files for the `record` cassette round-trip
# tests (hand-rolled temp_dir paths leak and race under parallel test runs).
tempfile = "3"

[target.'cfg(windows)'.dev-dependencies]
# Liveness probe (OpenProcess) in the integration test that proves a grandchild
# is reaped with its job — integration tests can't see the non-dev windows-sys.
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Threading"] }

[target.'cfg(unix)'.dev-dependencies]
# Liveness probe (kill(pid, 0)) and the root gate (geteuid) in the setsid /
# privilege-drop integration tests — the test crate can't see the non-dev libc.
libc = "0.2"
