Build Orchestration & Variants
Relevant source files
The following files were used as context for generating this wiki page:
The Build Orchestration system in Keynobi manages the lifecycle of Android Gradle tasks, ranging from initial variant discovery to execution and log persistence. It provides a bridge between the SolidJS frontend and the Rust backend, ensuring that build logs are streamed in real-time while maintaining a structured history of past executions.
The system is responsible for detecting build types and product flavors from build.gradle(.kts) files, calculating the resulting variants, and executing the corresponding Gradle wrapper (gradlew) commands. It handles process management, including graceful cancellation, and performs post-build analysis to identify compiler errors and warnings. Sources: README.md, src-tauri/src/services/build_runner.rs
Variant Management & Discovery
Variant discovery is handled through two primary mechanisms: a fast static analysis of Gradle files and an authoritative discovery via Gradle tasks.
Static Variant Parsing
The variant_manager.rs service parses build.gradle.kts content to extract buildTypes and productFlavors. It calculates the Cartesian product of flavors across defined dimensions to determine all possible build variants. If buildTypes are present but "debug" or "release" are missing, they are implicitly added as Android Gradle Plugin (AGP) defaults. Sources: src-tauri/src/services/variant_manager.rs:46-95
Task-Based Discovery
For cases where static parsing is insufficient, the system invokes ./gradlew :app:tasks and parses the output using regex to identify assemble and install tasks. It filters out test-related tasks (e.g., AndroidTest, UnitTest) to ensure only selectable user variants are presented. Sources: src-tauri/src/services/variant_manager.rs:207-250
The diagram above illustrates the fallback logic for discovering build variants.
Sources: src-tauri/src/services/variant_manager.rs
Build Execution Pipeline
The execution of a build task involves spawning a child process, capturing its output, and maintaining reactive state in the frontend.
Process Spawning
The run_gradle_task command validates the task name against an allowlist (alphanumeric, colons, hyphens, underscores) before spawning the process. It injects environment variables like JAVA_HOME and ANDROID_HOME derived from the application settings. Sources: src-tauri/src/commands/build.rs:43-80, src-tauri/src/services/build_runner.rs:356-380
Real-time Log Streaming
Build output is captured line-by-line. In the frontend, lines are buffered and flushed every 50ms to the buildLogStore to prevent UI thread saturation. Each line is classified into types such as Error, Warning, TaskStart, TaskEnd, or Summary. Sources: src/stores/build.store.ts:86-118, src-tauri/src/commands/build.rs:104-135
Build Results & History
Upon completion, the record_build_result function snapshots the logs, updates the build status (Success/Failed/Cancelled), and persists a BuildRecord to a JSON history file. History is capped at a maximum of 10 records in-memory and 20 on disk. Sources: src-tauri/src/services/build_runner.rs:17-45, src-tauri/src/services/build_runner.rs:310-348
| Data Structure | Type | Description |
|---|---|---|
BuildPhase | Union | idle, running, success, failed, cancelled |
BuildLine | Interface | Raw content, file path, line/col numbers, and line kind |
BuildRecord | Interface | Persisted summary including ID, task, status, and project root |
BuildResult | Interface | Final counts of errors, warnings, and duration |
| Sources: src/stores/build.store.ts:7-21, src/bindings/BuildResult.ts |
Log Persistence & Rotation
Keynobi maintains a structured log for every build to support later inspection via the MCP server or UI history.
- Storage: Logs are stored as
.jsonl(JSON Lines) files in~/.keynobi/build-logs/namedbuild-{id}.jsonl. - Parsing: Every raw line from the build process is re-parsed into a structured
BuildLinebefore being written to disk. - Rotation: The system performs a three-pass rotation:
- Age: Deletes logs older than the configured retention days.
- Orphans: Deletes logs whose build IDs no longer exist in the history registry.
- Size: Deletes the oldest logs until the folder size is under the configured MB limit.
Sources: src-tauri/src/services/build_runner.rs:105-167, src-tauri/src/commands/build.rs:309-325
Sequence of events from task initiation to final log persistence.
Sources: src-tauri/src/commands/build.rs, src/stores/build.store.ts
Key Data Models
Build Status Implementation
The BuildStatus enum in the Rust backend represents the state machine of a build.
// src-tauri/src/models/build.rs (Referenced in build_runner.rs)
pub enum BuildStatus {
Idle,
Running { task: String, started_at: String },
Success(BuildResult),
Failed(BuildResult),
Cancelled,
}
Sources: src-tauri/src/services/build_runner.rs:71-78, src-tauri/src/commands/build.rs:255-260
Summary
The Build Orchestration system provides a robust, asynchronous pipeline for managing Android builds. By combining static analysis for quick variant previews with authoritative Gradle task discovery, it ensures accuracy while maintaining performance. The implementation of ring-buffered logs and automated rotation ensures that developers have access to detailed build diagnostics without unbounded disk usage.