diff --git a/.github/workflows/ci.yml b/.github/workflows/check.yaml
similarity index 94%
rename from .github/workflows/ci.yml
rename to .github/workflows/check.yaml
index 361d1f0..b9486c3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/check.yaml
@@ -1,13 +1,16 @@
-name: Check, Build and Test
+name: Quality Check
on:
+ workflow_call:
push:
+ branches:
+ - main
pull_request:
branches:
- main
jobs:
- ci:
+ check:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..799a113
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,64 @@
+name: Deploy Documentation
+
+on:
+ push:
+ branches:
+ - '**'
+ tags-ignore:
+ - 'v*'
+ - 'v*-pre.*'
+ pull_request:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: 'pages'
+ cancel-in-progress: false
+
+jobs:
+ quality-check:
+ uses: ./.github/workflows/check.yaml
+
+ build-doc:
+ name: Build documentation
+ runs-on: ubuntu-latest
+ needs: quality-check
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Install dependencies
+ run: sudo apt-get update && sudo apt-get install -y libudev-dev
+
+ - uses: Swatinem/rust-cache@v2
+
+ - name: Build docs
+ run: cargo doc --no-deps --workspace
+
+ - name: Create index redirect
+ run: |
+ printf '
Redirecting to ostool documentation...
' > target/doc/index.html
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: target/doc
+
+ deploy-doc:
+ name: Deploy to GitHub Pages
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ needs: build-doc
+ if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/Cargo.lock b/Cargo.lock
index e2a5db3..5e0021c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -945,7 +945,7 @@ checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "fitimage"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"anyhow",
"byteorder",
@@ -1539,7 +1539,7 @@ dependencies = [
[[package]]
name = "jkconfig"
-version = "0.1.4"
+version = "0.1.5"
dependencies = [
"anyhow",
"axum",
@@ -1910,7 +1910,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "ostool"
-version = "0.8.8"
+version = "0.8.9"
dependencies = [
"anyhow",
"byte-unit",
@@ -3167,7 +3167,7 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "uboot-shell"
-version = "0.2.0"
+version = "0.2.1"
dependencies = [
"colored",
"env_logger",
diff --git a/fitimage/Cargo.toml b/fitimage/Cargo.toml
index d518473..2ffbb96 100644
--- a/fitimage/Cargo.toml
+++ b/fitimage/Cargo.toml
@@ -1,10 +1,12 @@
[package]
name = "fitimage"
-version = "0.1.0"
+version = "0.1.1"
edition = "2021"
authors = ["周睿 "]
description = "A Rust library for creating U-Boot compatible FIT images"
+documentation = "https://docs.rs/fitimage"
license = "MIT OR Apache-2.0"
+readme = "README.md"
categories = ["embedded", "development-tools", "os"]
keywords = ["u-boot", "boot", "fit-image", "embedded"]
repository = "https://github.com/ZR233/ostool"
diff --git a/fitimage/README.md b/fitimage/README.md
index 46cd19f..2fea9b2 100644
--- a/fitimage/README.md
+++ b/fitimage/README.md
@@ -1,6 +1,6 @@
-# mkimage - FIT Image Library
+# fitimage - FIT Image Library
-一个用于创建U-Boot兼容FIT (Flattened Image Tree) 镜像的Rust库。
+一个用于创建 U-Boot 兼容 FIT (Flattened Image Tree) 镜像的 Rust 库。
## 特性
@@ -18,13 +18,13 @@
```toml
[dependencies]
-mkimage = "0.1.0"
+fitimage = "0.1.0"
```
### 基本用法
```rust
-use mkimage::{FitImageBuilder, FitImageConfig, ComponentConfig};
+use fitimage::{ComponentConfig, FitImageBuilder, FitImageConfig};
// 创建FIT镜像配置
let config = FitImageConfig::new("My FIT Image")
@@ -32,12 +32,13 @@ let config = FitImageConfig::new("My FIT Image")
ComponentConfig::new("kernel", kernel_data)
.with_load_address(0x80080000)
.with_entry_point(0x80080000)
+ .with_compression(true)
)
.with_fdt(
ComponentConfig::new("fdt", fdt_data)
.with_load_address(0x82000000)
)
- .with_kernel_compression(true);
+ ;
// 构建FIT镜像
let mut builder = FitImageBuilder::new();
@@ -59,10 +60,13 @@ pub struct FitImageConfig {
pub kernel: Option,
pub fdt: Option,
pub ramdisk: Option,
- pub compress_kernel: bool,
+ pub default_config: Option,
+ pub configurations: std::collections::HashMap,
}
```
+> `configurations` 用于生成多个启动配置;当未设置时会自动生成默认配置。
+
### ComponentConfig
单个组件的配置:
@@ -84,20 +88,15 @@ pub struct ComponentConfig {
impl FitImageBuilder {
pub fn new() -> Self;
pub fn build(&mut self, config: FitImageConfig) -> Result>;
- pub fn build_with_compressor(
- &mut self,
- config: FitImageConfig,
- compressor: Box
- ) -> Result>;
}
```
## 示例
-### 完整FIT镜像
+### 完整 FIT 镜像
```rust
-use mkimage::{FitImageBuilder, FitImageConfig, ComponentConfig};
+use fitimage::{ComponentConfig, FitImageBuilder, FitImageConfig};
fn create_complete_fit() -> Result<(), Box> {
let config = FitImageConfig::new("Complete FIT Image")
@@ -105,6 +104,7 @@ fn create_complete_fit() -> Result<(), Box> {
ComponentConfig::new("linux", kernel_data)
.with_load_address(0x80080000)
.with_entry_point(0x80080000)
+ .with_compression(true)
)
.with_fdt(
ComponentConfig::new("devicetree", fdt_data)
@@ -114,7 +114,7 @@ fn create_complete_fit() -> Result<(), Box> {
ComponentConfig::new("initramfs", ramdisk_data)
.with_load_address(0x84000000)
)
- .with_kernel_compression(true);
+ ;
let mut builder = FitImageBuilder::new();
let fit_data = builder.build(config)?;
@@ -131,33 +131,41 @@ fn create_complete_fit() -> Result<(), Box> {
```rust
let config = FitImageConfig::new("Compressed FIT")
- .with_kernel(kernel_component)
- .with_kernel_compression(true); // 启用gzip压缩
+ .with_kernel(kernel_component.with_compression(true)); // 启用gzip压缩
```
## 兼容性
-- ✅ U-Boot FIT格式兼容
+- ✅ U-Boot FIT 格式兼容
- ✅ 标准设备树结构
- ✅ ARM64架构支持
- ✅ Linux OS支持
+## TODO
+
+- [ ] 增加 bzip2 压缩支持
+- [ ] 增加 lzma 压缩支持
+
## 构建和测试
```bash
# 构建库
cargo build --lib
-# 运行测试
-cargo test --lib
+# 运行测试(含单元测试与集成测试)
+cargo test
-# 运行示例
-cargo run --example basic_usage
-cargo run --example compression_test
-cargo run --example full_fit_test
+# 仅运行文档测试
+cargo test --doc
```
-## API文档
+## 测试建议
+
+- 单元测试:覆盖哈希/CRC 计算、FDT 字符串表对齐、配置构建边界值。
+- 功能测试:使用 `mkimage`/`dumpimage` 对照验证结构与字段一致性。
+- 文档测试:为关键公开 API 添加可运行示例,保证 doctest 通过。
+
+## API 文档
运行以下命令生成API文档:
diff --git a/fitimage/src/compression/gzip.rs b/fitimage/src/compression/gzip.rs
index be14707..1e911cd 100644
--- a/fitimage/src/compression/gzip.rs
+++ b/fitimage/src/compression/gzip.rs
@@ -1,6 +1,6 @@
-//! Gzip压缩实现
+//! Gzip compression implementation.
//!
-//! 使用flate2库提供gzip压缩和解压缩功能
+//! Provides gzip compression and decompression functionality using the flate2 library.
use std::io::{Read, Write};
@@ -8,31 +8,34 @@ use crate::compression::traits::CompressionInterface;
use crate::error::Result;
use flate2::{read::GzDecoder, write::GzEncoder, Compression as GzipLevel};
-/// Gzip压缩器
-/// 支持可配置的压缩级别
+/// Gzip compressor with configurable compression level.
pub struct GzipCompressor {
- /// 压缩级别 (0-9, 0表示无压缩)
+ /// Compression level (0-9, where 0 means no compression).
level: u8,
- /// 是否启用压缩(false时直接复制数据)
+ /// Whether compression is enabled (false means data is copied directly).
enabled: bool,
}
impl Default for GzipCompressor {
fn default() -> Self {
- Self::new(6) //
+ Self::new(6)
}
}
impl GzipCompressor {
- /// 创建指定压缩级别的gzip压缩器
+ /// Creates a new gzip compressor with the specified compression level.
+ ///
+ /// # Arguments
+ ///
+ /// * `level` - Compression level from 0 to 9. Level 0 disables compression.
pub fn new(level: u8) -> Self {
Self {
- level: level.clamp(0, 9), // 限制在0-9范围内
- enabled: level > 0, // 级别0表示不压缩
+ level: level.clamp(0, 9),
+ enabled: level > 0,
}
}
- /// 创建禁用压缩的实例
+ /// Creates a disabled compressor instance that passes data through unchanged.
pub fn new_disabled() -> Self {
Self {
level: 0,
@@ -40,7 +43,7 @@ impl GzipCompressor {
}
}
- /// 获取flate2的压缩级别
+ /// Gets the flate2 compression level.
fn get_compression_level(&self) -> GzipLevel {
if !self.enabled {
GzipLevel::none()
@@ -58,7 +61,7 @@ impl GzipCompressor {
impl CompressionInterface for GzipCompressor {
fn compress(&self, data: &[u8]) -> Result> {
if !self.enabled {
- // 如果禁用压缩,直接返回数据副本
+ // If compression is disabled, return a copy of the data.
return Ok(data.to_vec());
}
@@ -75,7 +78,7 @@ impl CompressionInterface for GzipCompressor {
fn decompress(&self, compressed_data: &[u8]) -> Result> {
if !self.enabled {
- // 如果没有压缩,直接返回数据副本
+ // If compression was not applied, return a copy of the data.
return Ok(compressed_data.to_vec());
}
diff --git a/fitimage/src/compression/mod.rs b/fitimage/src/compression/mod.rs
index 824c06a..4995989 100644
--- a/fitimage/src/compression/mod.rs
+++ b/fitimage/src/compression/mod.rs
@@ -1,6 +1,6 @@
-//! 压缩功能模块
+//! Compression module.
//!
-//! 提供各种压缩算法的统一接口,支持gzip、bzip2、lzma等格式
+//! Provides unified interface for compression algorithms. Currently supports gzip.
pub mod gzip;
pub mod traits;
diff --git a/fitimage/src/compression/traits.rs b/fitimage/src/compression/traits.rs
index 1567792..7bd8879 100644
--- a/fitimage/src/compression/traits.rs
+++ b/fitimage/src/compression/traits.rs
@@ -1,30 +1,35 @@
-//! 压缩接口定义
+//! Compression interface definitions.
//!
-//! 定义了所有压缩算法需要实现的标准接口
+//! Defines the standard interface that all compression algorithms must implement.
use crate::error::Result;
-/// 压缩接口trait
-/// 所有压缩算法都需要实现这个接口
+/// Compression interface trait.
+///
+/// All compression algorithms must implement this interface.
pub trait CompressionInterface {
- /// 压缩数据
+ /// Compresses data.
///
- /// # 参数
- /// - `data`: 要压缩的原始数据
+ /// # Arguments
///
- /// # 返回
- /// 压缩后的数据
+ /// * `data` - The raw data to compress
+ ///
+ /// # Returns
+ ///
+ /// The compressed data.
fn compress(&self, data: &[u8]) -> Result>;
- /// 解压缩数据(主要用于验证)
+ /// Decompresses data (mainly used for verification).
+ ///
+ /// # Arguments
+ ///
+ /// * `compressed_data` - The compressed data
///
- /// # 参数
- /// - `compressed_data`: 已压缩的数据
+ /// # Returns
///
- /// # 返回
- /// 解压缩后的原始数据
+ /// The decompressed original data.
fn decompress(&self, compressed_data: &[u8]) -> Result>;
- /// 获取压缩算法名称
+ /// Returns the name of the compression algorithm.
fn get_name(&self) -> &'static str;
}
diff --git a/fitimage/src/error.rs b/fitimage/src/error.rs
index c471561..bf66141 100644
--- a/fitimage/src/error.rs
+++ b/fitimage/src/error.rs
@@ -1,11 +1,11 @@
-//! Error types for mkimage operations
+//! Error types for fitimage operations
use thiserror::Error;
-/// Result type alias for mkimage operations
+/// Result type alias for fitimage operations
pub type Result = std::result::Result;
-/// Errors that can occur during mkimage operations
+/// Errors that can occur during fitimage operations
#[derive(Error, Debug)]
pub enum MkImageError {
#[error("Invalid image data: {0}")]
diff --git a/fitimage/src/fit/config.rs b/fitimage/src/fit/config.rs
index 7d45ef1..66d0699 100644
--- a/fitimage/src/fit/config.rs
+++ b/fitimage/src/fit/config.rs
@@ -4,12 +4,15 @@
use serde::{Deserialize, Serialize};
+/// Supported compression algorithms for FIT components.
#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq)]
pub enum CompressionAlgorithm {
+ /// Gzip compression.
Gzip,
}
impl CompressionAlgorithm {
+ /// Return the string name used in FIT properties.
pub fn as_str(&self) -> &'static str {
match self {
CompressionAlgorithm::Gzip => "gzip",
@@ -17,7 +20,7 @@ impl CompressionAlgorithm {
}
}
-/// Configuration for building a FIT image
+/// Configuration for building a FIT image.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FitImageConfig {
/// Description of the FIT image
@@ -35,16 +38,22 @@ pub struct FitImageConfig {
/// Default configuration name
pub default_config: Option,
- /// Configurations mapping (name -> (description, kernel, fdt, ramdisk))
+ /// Configurations mapping (name -> description, kernel, fdt, ramdisk).
pub configurations: std::collections::HashMap,
}
+/// A named configuration that references image nodes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FitConfiguration {
+ /// Configuration node name.
pub name: String,
+ /// Human-readable configuration description.
pub description: String,
+ /// Kernel image node reference.
pub kernel: Option,
+ /// FDT image node reference.
pub fdt: Option,
+ /// Ramdisk image node reference.
pub ramdisk: Option,
}
@@ -69,6 +78,7 @@ pub struct ComponentConfig {
/// OS type (linux, etc.)
pub os: Option,
+ /// Whether to gzip-compress this component before embedding.
pub compression: bool,
/// Load address in memory
@@ -118,7 +128,7 @@ impl ComponentConfig {
self
}
- /// Set compression type
+ /// Enable or disable gzip compression for this component.
pub fn with_compression(mut self, b: bool) -> Self {
self.compression = b;
self
@@ -138,7 +148,7 @@ impl ComponentConfig {
}
impl FitImageConfig {
- /// Create a new FIT image configuration
+ /// Create a new FIT image configuration with a top-level description.
pub fn new(description: impl Into) -> Self {
Self {
description: description.into(),
@@ -150,31 +160,31 @@ impl FitImageConfig {
}
}
- /// Set kernel component
+ /// Set kernel component.
pub fn with_kernel(mut self, kernel: ComponentConfig) -> Self {
self.kernel = Some(kernel);
self
}
- /// Set FDT component
+ /// Set FDT component.
pub fn with_fdt(mut self, fdt: ComponentConfig) -> Self {
self.fdt = Some(fdt);
self
}
- /// Set ramdisk component
+ /// Set ramdisk component.
pub fn with_ramdisk(mut self, ramdisk: ComponentConfig) -> Self {
self.ramdisk = Some(ramdisk);
self
}
- /// Set default configuration
+ /// Set default configuration name.
pub fn with_default_config(mut self, default: impl Into) -> Self {
self.default_config = Some(default.into());
self
}
- /// Add a configuration
+ /// Add a configuration entry that references image node names.
pub fn with_configuration(
mut self,
name: impl Into,
diff --git a/fitimage/src/fit/fdt_header.rs b/fitimage/src/fit/fdt_header.rs
index ecfa17c..7fc25e6 100644
--- a/fitimage/src/fit/fdt_header.rs
+++ b/fitimage/src/fit/fdt_header.rs
@@ -256,7 +256,7 @@ mod tests {
assert_eq!(MemReserveEntry::size(), 16); // 2 * 8 bytes
}
- #[test]
+ // #[test]
// fn test_mem_reserve_entry_write() {
// let entry = MemReserveEntry::new(0x12345678, 0xABCDEF00);
// let mut buffer = Vec::new();
diff --git a/fitimage/src/fit/mod.rs b/fitimage/src/fit/mod.rs
index 9e68ac7..8a49916 100644
--- a/fitimage/src/fit/mod.rs
+++ b/fitimage/src/fit/mod.rs
@@ -1,6 +1,6 @@
-//! FIT (Flattened Image Tree) 模块
+//! FIT (Flattened Image Tree) module.
//!
-//! 实现U-Boot FIT image格式的创建和处理功能
+//! Provides functionality for creating and processing U-Boot FIT image format.
pub mod builder;
pub mod config;
diff --git a/fitimage/src/lib.rs b/fitimage/src/lib.rs
index b5bca9b..d5d9cf6 100644
--- a/fitimage/src/lib.rs
+++ b/fitimage/src/lib.rs
@@ -1,7 +1,58 @@
+//! # fitimage - FIT Image Library
+//!
+//! A Rust library for creating U-Boot compatible FIT (Flattened Image Tree) images.
+//!
+//! ## Features
+//!
+//! - Complete FIT image creation functionality
+//! - Support for kernel, FDT (device tree), and ramdisk components
+//! - Gzip compression support
+//! - Multiple hash algorithms (MD5, SHA1, CRC32)
+//! - U-Boot compatible device tree structure
+//!
+//! ## Quick Start
+//!
+//! ```rust,no_run
+//! use fitimage::{FitImageBuilder, FitImageConfig, ComponentConfig};
+//!
+//! // Create FIT image configuration
+//! let config = FitImageConfig::new("My FIT Image")
+//! .with_kernel(
+//! ComponentConfig::new("kernel", vec![/* kernel data */])
+//! .with_load_address(0x80080000)
+//! .with_entry_point(0x80080000)
+//! )
+//! .with_fdt(
+//! ComponentConfig::new("fdt", vec![/* fdt data */])
+//! .with_load_address(0x82000000)
+//! );
+//!
+//! // Build FIT image
+//! let mut builder = FitImageBuilder::new();
+//! let fit_data = builder.build(config).unwrap();
+//! ```
+//!
+//! ## Modules
+//!
+//! - [`fit`] - Core FIT image building functionality
+//! - [`compression`] - Compression algorithms (gzip)
+//! - [`hash`] - Hash calculation utilities (MD5, SHA1, CRC32)
+//! - [`crc`] - CRC32 checksum calculation
+//! - [`error`] - Error types and result definitions
+
+/// Compression algorithms support (gzip, etc.)
pub mod compression;
+
+/// CRC32 checksum calculation utilities.
pub mod crc;
+
+/// Error types and result definitions for FIT image operations.
pub mod error;
+
+/// Core FIT image building functionality.
pub mod fit;
+
+/// Hash calculation utilities (MD5, SHA1, CRC32).
pub mod hash;
// Re-export main types for convenience
@@ -11,7 +62,7 @@ pub use error::{MkImageError, Result};
pub use fit::{ComponentConfig, FitImageBuilder, FitImageConfig};
pub use hash::{calculate_hashes, default_hash_algorithms, HashAlgorithm, HashResult};
-/// Current version of the mkimage implementation
+/// Current version of the fitimage implementation
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// FIT image magic number
diff --git a/jkconfig/Cargo.toml b/jkconfig/Cargo.toml
index c830ac6..6ec7926 100644
--- a/jkconfig/Cargo.toml
+++ b/jkconfig/Cargo.toml
@@ -2,12 +2,14 @@
authors = ["周睿 "]
categories = ["command-line-interface", "config"]
description = "A Cursive-based TUI component library for JSON Schema configuration"
+documentation = "https://docs.rs/jkconfig"
edition = "2024"
keywords = ["tui", "config", "json-schema", "terminal"]
license = "MIT OR Apache-2.0"
name = "jkconfig"
+readme = "README.md"
repository = "https://github.com/ZR233/ostool"
-version = "0.1.4"
+version = "0.1.5"
[[bin]]
name = "jkconfig"
diff --git a/jkconfig/src/data/app_data.rs b/jkconfig/src/data/app_data.rs
index 2856c70..f985231 100644
--- a/jkconfig/src/data/app_data.rs
+++ b/jkconfig/src/data/app_data.rs
@@ -11,29 +11,43 @@ use cursive::Cursive;
use crate::data::{menu::MenuRoot, types::ElementType};
+/// Callback used to provide the list of available features.
pub type FeaturesCallback = Arc Vec + Send + Sync>;
+/// Callback invoked when a menu element is entered.
pub type HockCallback = Arc;
+/// Hook registration for a specific menu path.
#[derive(Clone)]
pub struct ElemHock {
+ /// Menu path string (dot-separated).
pub path: String,
+ /// Callback executed when entering the path.
pub callback: HockCallback,
}
+/// Application state container for schema-driven config editing.
#[derive(Clone)]
pub struct AppData {
+ /// Root menu parsed from JSON Schema.
pub root: MenuRoot,
+ /// Current menu path as a list of keys.
pub current_key: Vec,
+ /// Whether configuration has pending changes.
pub needs_save: bool,
+ /// Path to the configuration file.
pub config: PathBuf,
+ /// Custom user data storage.
pub user_data: HashMap,
+ /// Temporary data used by editors.
pub temp_data: Option<(String, serde_json::Value)>,
+ /// Registered element hooks.
pub elem_hocks: Vec,
}
const DEFAULT_CONFIG_PATH: &str = ".config.toml";
+/// Derive a default schema path from a config path.
pub fn default_schema_by_init(config: &Path) -> PathBuf {
let binding = config.file_name().unwrap().to_string_lossy();
let mut name_split = binding.split(".").collect::>();
@@ -51,6 +65,9 @@ pub fn default_schema_by_init(config: &Path) -> PathBuf {
}
impl AppData {
+ /// Build `AppData` from optional config and schema paths.
+ ///
+ /// When schema is not provided, it is auto-derived from the config path.
pub fn new(
config: Option>,
schema: Option>,
@@ -80,6 +97,9 @@ impl AppData {
init_value_path
}
+ /// Build `AppData` from an initial content string and a schema value.
+ ///
+ /// This is useful when config content has already been loaded.
pub fn new_with_init_and_schema(
init: &str,
init_value_path: &Path,
@@ -116,6 +136,9 @@ impl AppData {
})
}
+ /// Build `AppData` from a schema value and an optional config path.
+ ///
+ /// If the config file exists, it is loaded to initialize values.
pub fn new_with_schema(
config: Option>,
schema: &serde_json::Value,
@@ -156,6 +179,7 @@ impl AppData {
})
}
+ /// Persist changes and create a timestamped backup when needed.
pub fn on_exit(&mut self) -> anyhow::Result<()> {
if !self.needs_save {
return Ok(());
@@ -193,6 +217,7 @@ impl AppData {
Ok(())
}
+ /// Enter a submenu path (dot-separated).
pub fn enter(&mut self, key: &str) {
if key.is_empty() {
return;
@@ -200,17 +225,19 @@ impl AppData {
self.current_key = key.split(".").map(|s| s.to_string()).collect();
}
+ /// Push a field name onto the current path.
pub fn push_field(&mut self, f: &str) {
self.current_key.push(f.to_string());
}
- /// 返回上级路径
+ /// Navigate back to the parent path.
pub fn navigate_back(&mut self) {
if !self.current_key.is_empty() {
self.current_key.pop();
}
}
+ /// Return the current path as a dot-separated string.
pub fn key_string(&self) -> String {
if self.current_key.is_empty() {
return String::new();
@@ -219,10 +246,12 @@ impl AppData {
self.current_key.join(".")
}
+ /// Get the element at the current path.
pub fn current(&self) -> Option<&ElementType> {
self.root.get_by_key(&self.key_string())
}
+ /// Get the mutable element at the current path.
pub fn current_mut(&mut self) -> Option<&mut ElementType> {
self.root.get_mut_by_key(&self.key_string())
}
diff --git a/jkconfig/src/data/item.rs b/jkconfig/src/data/item.rs
index c4d837c..d1dac61 100644
--- a/jkconfig/src/data/item.rs
+++ b/jkconfig/src/data/item.rs
@@ -2,34 +2,42 @@ use crate::data::{schema::SchemaError, types::ElementBase};
use serde_json::Value;
+/// Leaf configuration item with a concrete value type.
#[derive(Debug, Clone)]
pub struct Item {
+ /// Shared element metadata.
pub base: ElementBase,
+ /// Value storage and type information.
pub item_type: ItemType,
}
+/// Supported value types for leaf items.
#[derive(Debug, Clone)]
pub enum ItemType {
+ /// String value with optional default.
String {
value: Option,
default: Option,
},
+ /// Floating-point number value with optional default.
Number {
value: Option,
default: Option,
},
+ /// Integer value with optional default.
Integer {
value: Option,
default: Option,
},
- Boolean {
- value: bool,
- default: bool,
- },
+ /// Boolean value with default.
+ Boolean { value: bool, default: bool },
+ /// Enum selection by index.
Enum(EnumItem),
+ /// Array of scalar values stored as strings.
Array(ArrayItem),
}
+/// Array item metadata and values.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ArrayItem {
/// Array element type (e.g., "string", "integer")
@@ -40,19 +48,27 @@ pub struct ArrayItem {
pub default: Vec,
}
+/// Enum variants and selected index.
#[derive(Debug, Clone)]
pub struct EnumItem {
+ /// List of variant labels.
pub variants: Vec,
+ /// Selected variant index.
pub value: Option,
+ /// Default variant index.
pub default: Option,
}
impl EnumItem {
+ /// Get the currently selected variant as string, if any.
pub fn value_str(&self) -> Option<&str> {
self.value
.and_then(|idx| self.variants.get(idx).map(String::as_str))
}
+ /// Update enum selection from JSON value.
+ ///
+ /// Accepts string values matching a variant or numeric indices.
pub fn update_from_value(&mut self, value: &Value, path: &str) -> Result<(), SchemaError> {
match value {
Value::String(s) => {
@@ -98,6 +114,7 @@ impl EnumItem {
}
impl ItemType {
+ /// Update the stored value from JSON.
pub fn update_from_value(&mut self, value: &Value, path: &str) -> Result<(), SchemaError> {
match self {
ItemType::String {
@@ -204,6 +221,7 @@ impl ItemType {
}
impl Item {
+ /// Serialize the item into a JSON value.
pub fn as_json(&self) -> Value {
match &self.item_type {
ItemType::String { value, .. } => match value {
@@ -250,6 +268,7 @@ impl Item {
}
}
+ /// Update the item from a JSON value.
pub fn update_from_value(&mut self, value: &Value) -> Result<(), SchemaError> {
let path = self.base.key();
self.item_type.update_from_value(value, &path)
diff --git a/jkconfig/src/data/menu.rs b/jkconfig/src/data/menu.rs
index 907b821..07e523e 100644
--- a/jkconfig/src/data/menu.rs
+++ b/jkconfig/src/data/menu.rs
@@ -11,14 +11,19 @@ use crate::data::{
types::{ElementBase, ElementType},
};
+/// Root container for schema-derived menu tree.
#[derive(Clone)]
pub struct MenuRoot {
+ /// JSON Schema version string.
pub schema_version: String,
+ /// Root title displayed in the UI.
pub title: String,
+ /// Root element (must be a menu).
pub menu: ElementType,
}
impl MenuRoot {
+ /// Get the root menu node (panics if root is not a menu).
pub fn menu(&self) -> &Menu {
match &self.menu {
ElementType::Menu(menu) => menu,
@@ -26,6 +31,7 @@ impl MenuRoot {
}
}
+ /// Get the mutable root menu node (panics if root is not a menu).
pub fn menu_mut(&mut self) -> &mut Menu {
match &mut self.menu {
ElementType::Menu(menu) => menu,
@@ -33,6 +39,7 @@ impl MenuRoot {
}
}
+ /// Get an element by its dot-separated key.
pub fn get_by_key(&self, key: &str) -> Option<&ElementType> {
if key.is_empty() {
return Some(&self.menu);
@@ -42,6 +49,7 @@ impl MenuRoot {
self.menu().get_by_field_path(&ks)
}
+ /// Get a mutable element by its dot-separated key.
pub fn get_mut_by_key(&mut self, key: &str) -> Option<&mut ElementType> {
if key.is_empty() {
return Some(&mut self.menu);
@@ -50,10 +58,12 @@ impl MenuRoot {
self.menu_mut().get_mut_by_field_path(&ks)
}
+ /// Update the menu tree from a JSON object value.
pub fn update_by_value(&mut self, value: &Value) -> Result<(), SchemaError> {
self.menu.update_from_value(value, None)
}
+ /// Serialize the menu tree into a JSON value.
pub fn as_json(&self) -> Value {
self.menu().as_json()
}
@@ -74,15 +84,19 @@ impl Debug for MenuRoot {
}
}
-/// Menu => type: object
+/// Menu node for schema objects (type: object).
#[derive(Clone)]
pub struct Menu {
+ /// Shared element metadata.
pub base: ElementBase,
+ /// Child elements for each field.
pub children: Vec,
+ /// Whether this menu has been set by user input.
pub is_set: bool,
}
impl Menu {
+ /// Serialize this menu into a JSON object.
pub fn as_json(&self) -> Value {
let mut result = serde_json::Map::new();
@@ -124,6 +138,7 @@ impl Menu {
Value::Object(result)
}
+ /// Get a child element by its path segments.
pub fn get_by_field_path(&self, field_path: &[&str]) -> Option<&ElementType> {
if field_path.is_empty() {
return None;
@@ -144,6 +159,7 @@ impl Menu {
}
}
+ /// Get a mutable child element by its path segments.
pub fn get_mut_by_field_path(&mut self, field_path: &[&str]) -> Option<&mut ElementType> {
if field_path.is_empty() {
return None;
@@ -164,6 +180,7 @@ impl Menu {
}
}
+ /// Update this menu from a JSON object value.
pub fn update_from_value(&mut self, value: &Value) -> Result<(), SchemaError> {
let value = value.as_object().ok_or(SchemaError::TypeMismatch {
path: self.key(),
@@ -183,6 +200,7 @@ impl Menu {
Ok(())
}
+ /// Whether this menu is considered unset.
pub fn is_none(&self) -> bool {
if self.is_required {
return false;
@@ -190,10 +208,12 @@ impl Menu {
!self.is_set
}
+ /// Return a copy of child elements for UI rendering.
pub fn fields(&self) -> Vec {
self.children.to_vec()
}
+ /// Get a direct child by field name.
pub fn get_child_by_key(&self, key: &str) -> Option<&ElementType> {
self.children
.iter()
@@ -201,6 +221,7 @@ impl Menu {
.map(|v| v as _)
}
+ /// Get a mutable direct child by field name.
pub fn get_child_mut_by_key(&mut self, key: &str) -> Option<&mut ElementType> {
self.children
.iter_mut()
diff --git a/jkconfig/src/data/mod.rs b/jkconfig/src/data/mod.rs
index a749872..8576940 100644
--- a/jkconfig/src/data/mod.rs
+++ b/jkconfig/src/data/mod.rs
@@ -1,8 +1,39 @@
+//! Configuration data structures and schema parsing.
+//!
+//! This module provides the core data structures for managing JSON Schema-based
+//! configuration, including:
+//!
+//! - Schema parsing and conversion to internal representation
+//! - Configuration value management
+//! - Serialization to TOML/JSON formats
+//!
+//! ## Architecture
+//!
+//! The data module is organized into several submodules:
+//!
+//! - [`app_data`] - Main application data container
+//! - [`item`] - Individual configuration items
+//! - [`menu`] - Menu structure for navigation
+//! - [`oneof`] - OneOf/AnyOf schema variant handling
+//! - [`schema`] - JSON Schema parsing utilities
+//! - [`types`] - Element type definitions
+
+/// Main application data container and configuration management.
pub mod app_data;
+
+/// Individual configuration item representation.
pub mod item;
+
+/// Menu structure for hierarchical navigation.
pub mod menu;
+
+/// OneOf/AnyOf schema variant handling.
pub mod oneof;
+
+/// JSON Schema parsing utilities.
pub mod schema;
+
+/// Element type definitions for different data types.
pub mod types;
pub use app_data::AppData;
diff --git a/jkconfig/src/data/oneof.rs b/jkconfig/src/data/oneof.rs
index 60489eb..b8e2ad8 100644
--- a/jkconfig/src/data/oneof.rs
+++ b/jkconfig/src/data/oneof.rs
@@ -12,24 +12,32 @@ use crate::data::{
use log::trace;
use serde_json::Value;
+/// OneOf/AnyOf variant container.
#[derive(Clone)]
pub struct OneOf {
+ /// Shared element metadata.
pub base: ElementBase,
+ /// Available variant elements.
pub variants: Vec,
+ /// Selected variant index.
pub selected_index: Option,
+ /// Default variant index.
pub default_index: Option,
}
impl OneOf {
+ /// Get the currently selected variant.
pub fn selected(&self) -> Option<&ElementType> {
self.selected_index.and_then(|idx| self.variants.get(idx))
}
+ /// Get the currently selected variant mutably.
pub fn selected_mut(&mut self) -> Option<&mut ElementType> {
self.selected_index
.and_then(move |idx| self.variants.get_mut(idx))
}
+ /// Resolve a display name for a variant index.
pub fn variant_display(&self, idx: usize) -> String {
if let Some(variant) = self.variants.get(idx) {
match variant {
@@ -49,6 +57,7 @@ impl OneOf {
}
}
+ /// Get an element by path segments from the selected variant.
pub fn get_by_field_path(&self, field_path: &[&str]) -> Option<&ElementType> {
if field_path.is_empty() {
return None;
@@ -78,6 +87,7 @@ impl OneOf {
None
}
+ /// Get a mutable element by path segments from the selected variant.
pub fn get_mut_by_field_path(&mut self, field_path: &[&str]) -> Option<&mut ElementType> {
if field_path.is_empty() {
return None;
@@ -116,6 +126,7 @@ impl OneOf {
variant.update_from_value(value, name).is_ok()
}
+ /// Update selection and data from a JSON value.
pub fn update_from_value(&mut self, value: &Value) -> Result<(), SchemaError> {
let mut name: Option = None;
let mut value = value;
@@ -149,6 +160,7 @@ impl OneOf {
})
}
+ /// Serialize the selected variant into JSON.
pub fn as_json(&self) -> Value {
if let Some(selected) = self.selected() {
match selected {
@@ -174,10 +186,12 @@ impl OneOf {
}
}
+ /// Get the field name for this OneOf element.
pub fn field_name(&self) -> String {
self.base.field_name()
}
+ /// Select a variant by index and initialize defaults when needed.
pub fn set_selected_index(&mut self, index: usize) -> Result<(), SchemaError> {
let path = self.path.clone();
let v = self
@@ -201,6 +215,7 @@ impl OneOf {
Ok(())
}
+ /// Whether no variant is selected.
pub fn is_none(&self) -> bool {
self.selected_index.is_none()
}
diff --git a/jkconfig/src/data/schema.rs b/jkconfig/src/data/schema.rs
index 8fd6b1e..3129b67 100644
--- a/jkconfig/src/data/schema.rs
+++ b/jkconfig/src/data/schema.rs
@@ -9,6 +9,7 @@ use crate::data::{
types::{ElementBase, ElementType},
};
+/// Errors produced while converting JSON Schema into internal structures.
#[derive(thiserror::Error, Debug)]
pub enum SchemaError {
#[error("Unsupported schema")]
diff --git a/jkconfig/src/data/types.rs b/jkconfig/src/data/types.rs
index 483fe9f..55d542e 100644
--- a/jkconfig/src/data/types.rs
+++ b/jkconfig/src/data/types.rs
@@ -7,16 +7,23 @@ use crate::data::{item::Item, menu::Menu, oneof::OneOf, schema::SchemaError};
use serde_json::Value;
+/// Common fields shared by all schema elements.
#[derive(Debug, Clone, Default)]
pub struct ElementBase {
+ /// Schema path for this element.
pub path: PathBuf,
+ /// Display title derived from schema description or field name.
pub title: String,
+ /// Help text from schema description.
pub help: Option,
+ /// Whether this field is required by the schema.
pub is_required: bool,
+ /// Struct or variant name used for display.
pub struct_name: String,
}
impl ElementBase {
+ /// Create a new base element from schema metadata.
pub fn new(
path: &Path,
description: Option,
@@ -43,6 +50,7 @@ impl ElementBase {
}
}
+ /// Return the dot-separated key for this element.
pub fn key(&self) -> String {
self.path
.iter()
@@ -51,6 +59,7 @@ impl ElementBase {
.join(".")
}
+ /// Return the last path segment as the field name.
pub fn field_name(&self) -> String {
self.path
.iter()
@@ -60,6 +69,7 @@ impl ElementBase {
}
}
+/// High-level element types used by the UI and serialization logic.
#[derive(Debug, Clone)]
pub enum ElementType {
Menu(Menu),
@@ -90,6 +100,7 @@ impl DerefMut for ElementType {
}
impl ElementType {
+ /// Update the element value from a JSON value.
pub fn update_from_value(
&mut self,
value: &Value,
@@ -113,6 +124,7 @@ impl ElementType {
}
}
+ /// Whether this element is considered unset.
pub fn is_none(&self) -> bool {
match self {
ElementType::Menu(menu) => menu.is_none(),
@@ -128,6 +140,7 @@ impl ElementType {
}
}
+ /// Reset this element to an "unset" state when allowed.
pub fn set_none(&mut self) {
if self.is_required {
return;
diff --git a/jkconfig/src/lib.rs b/jkconfig/src/lib.rs
index 3aba6aa..381a5b4 100644
--- a/jkconfig/src/lib.rs
+++ b/jkconfig/src/lib.rs
@@ -1,15 +1,65 @@
+//! # jkconfig
+//!
+//! A Cursive-based TUI component library for JSON Schema configuration.
+//!
+//! JKConfig automatically generates interactive terminal forms from JSON Schema
+//! definitions, making configuration management intuitive and error-free.
+//!
+//! ## Features
+//!
+//! - Beautiful TUI interface built with [Cursive](https://github.com/gyscos/cursive)
+//! - JSON Schema driven UI generation (Draft 2020-12)
+//! - Support for multiple data types: String, Integer, Number, Boolean, Enum, Array, Object, OneOf
+//! - Multi-format support: TOML and JSON configuration files
+//! - Keyboard shortcuts with Vim-like keybindings
+//! - Real-time type validation based on schema constraints
+//! - Automatic backup before saving changes
+//!
+//! ## Quick Start
+//!
+//! ```rust,no_run
+//! use jkconfig::data::AppData;
+//!
+//! // Load configuration with schema
+//! let app_data = AppData::new(
+//! Some("config.toml"),
+//! Some("config-schema.json")
+//! ).unwrap();
+//!
+//! // Access the configuration tree
+//! let json_value = app_data.root.as_json();
+//! ```
+//!
+//! ## Modules
+//!
+//! - [`data`] - Configuration data structures and schema parsing
+//! - [`run`] - TUI application runner
+//! - [`ui`] - UI components and editors
+//! - [`web`] - Web server module (requires `web` feature)
+
// #[macro_use]
// extern crate log;
#[macro_use]
mod log;
+/// Configuration data structures and schema parsing.
+///
+/// This module provides the core data structures for managing configuration
+/// data, including schema parsing, value management, and serialization.
pub mod data;
+
// UI模块暂时注释掉,使用主程序中的 MenuView
+/// TUI application runner and main entry points.
pub mod run;
+
+/// UI components and editors for different data types.
pub mod ui;
// Web服务器模块(需要web feature)
+/// Web server module for remote configuration editing.
+///
+/// This module is only available when the `web` feature is enabled.
#[cfg(feature = "web")]
pub mod web;
diff --git a/jkconfig/src/run.rs b/jkconfig/src/run.rs
index 6dd054f..f0c8bce 100644
--- a/jkconfig/src/run.rs
+++ b/jkconfig/src/run.rs
@@ -13,6 +13,14 @@ use crate::{
pub use crate::data::app_data::ElemHock;
+/// Run the configuration editor workflow for a typed config.
+///
+/// When `always_use_ui` is false and the config file can be parsed,
+/// the parsed config is returned without launching the UI.
+///
+/// # Errors
+///
+/// Returns errors when schema generation, parsing, or I/O fails.
pub async fn run(
config_path: impl AsRef,
always_use_ui: bool,
diff --git a/ostool/Cargo.toml b/ostool/Cargo.toml
index b53c322..461d3d3 100644
--- a/ostool/Cargo.toml
+++ b/ostool/Cargo.toml
@@ -1,13 +1,15 @@
[package]
authors = ["周睿 "]
-categories = ["os", "embedded", "development-tools", "config"]
+categories = ["command-line-utilities", "embedded", "development-tools"]
description = "A tool for operating system development"
+documentation = "https://docs.rs/ostool"
edition = "2024"
+keywords = ["os", "embedded", "qemu", "u-boot", "bootloader"]
license = "MIT OR Apache-2.0"
name = "ostool"
readme = "../README.md"
repository = "https://github.com/ZR233/ostool"
-version = "0.8.8"
+version = "0.8.9"
[[bin]]
name = "ostool"
diff --git a/ostool/src/build/cargo_builder.rs b/ostool/src/build/cargo_builder.rs
index 1fa90ac..c57bd6a 100644
--- a/ostool/src/build/cargo_builder.rs
+++ b/ostool/src/build/cargo_builder.rs
@@ -1,3 +1,9 @@
+//! Cargo build command builder and executor.
+//!
+//! This module provides the [`CargoBuilder`] type for constructing and executing
+//! Cargo build commands with customizable options, environment variables, and
+//! pre/post build hooks.
+
use std::{
collections::HashMap,
path::{Path, PathBuf},
@@ -7,6 +13,21 @@ use colored::Colorize;
use crate::{build::config::Cargo, ctx::AppContext, utils::Command};
+/// A builder for constructing and executing Cargo commands.
+///
+/// `CargoBuilder` provides a fluent API for configuring Cargo build or run
+/// commands with custom arguments, environment variables, and build hooks.
+///
+/// # Example
+///
+/// ```rust,no_run
+/// use ostool::build::cargo_builder::CargoBuilder;
+/// use ostool::build::config::Cargo;
+/// use ostool::ctx::AppContext;
+///
+/// // CargoBuilder is typically used internally by AppContext
+/// // See AppContext::cargo_build() and AppContext::cargo_run()
+/// ```
pub struct CargoBuilder<'a> {
ctx: &'a mut AppContext,
config: &'a Cargo,
@@ -18,6 +39,13 @@ pub struct CargoBuilder<'a> {
}
impl<'a> CargoBuilder<'a> {
+ /// Creates a new `CargoBuilder` for executing `cargo build`.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The application context.
+ /// * `config` - The Cargo build configuration.
+ /// * `config_path` - Optional path to the configuration file.
pub fn build(ctx: &'a mut AppContext, config: &'a Cargo, config_path: Option) -> Self {
Self {
ctx,
@@ -30,6 +58,13 @@ impl<'a> CargoBuilder<'a> {
}
}
+ /// Creates a new `CargoBuilder` for executing `cargo run`.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The application context.
+ /// * `config` - The Cargo build configuration.
+ /// * `config_path` - Optional path to the configuration file.
pub fn run(ctx: &'a mut AppContext, config: &'a Cargo, config_path: Option) -> Self {
Self {
ctx,
@@ -42,30 +77,38 @@ impl<'a> CargoBuilder<'a> {
}
}
+ /// Returns `true` if this builder is configured for `cargo run`.
pub fn is_run(&self) -> bool {
self.command == "run"
}
+ /// Sets the debug mode for the build.
+ ///
+ /// When enabled, builds in debug mode and enables GDB server for QEMU.
pub fn debug(self, debug: bool) -> Self {
self.ctx.debug = debug;
self
}
+ /// Creates a build command using the context's stored config path.
pub fn build_auto(ctx: &'a mut AppContext, config: &'a Cargo) -> Self {
let config_path = ctx.build_config_path.clone();
Self::build(ctx, config, config_path)
}
+ /// Creates a run command using the context's stored config path.
pub fn run_auto(ctx: &'a mut AppContext, config: &'a Cargo) -> Self {
let config_path = ctx.build_config_path.clone();
Self::run(ctx, config, config_path)
}
+ /// Adds a single argument to the Cargo command.
pub fn arg(mut self, arg: impl Into) -> Self {
self.extra_args.push(arg.into());
self
}
+ /// Adds multiple arguments to the Cargo command.
pub fn args(mut self, args: I) -> Self
where
I: IntoIterator- ,
@@ -75,16 +118,26 @@ impl<'a> CargoBuilder<'a> {
self
}
+ /// Sets an environment variable for the Cargo command.
pub fn env(mut self, key: impl Into, value: impl Into) -> Self {
self.extra_envs.insert(key.into(), value.into());
self
}
+ /// Sets whether to skip the objcopy step after building.
pub fn skip_objcopy(mut self, skip: bool) -> Self {
self.skip_objcopy = skip;
self
}
+ /// Executes the configured Cargo command.
+ ///
+ /// This runs pre-build commands, executes Cargo, handles output artifacts,
+ /// and runs post-build commands.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if any step of the build process fails.
pub async fn execute(mut self) -> anyhow::Result<()> {
// 1. Pre-build commands
self.run_pre_build_cmds()?;
diff --git a/ostool/src/build/config.rs b/ostool/src/build/config.rs
index 72a0978..ef4e273 100644
--- a/ostool/src/build/config.rs
+++ b/ostool/src/build/config.rs
@@ -1,66 +1,113 @@
+//! Build configuration types and structures.
+//!
+//! This module defines the configuration structures used to specify how
+//! operating system projects should be built. Configuration is typically
+//! stored in `.build.toml` files.
+//!
+//! # Configuration File Format
+//!
+//! ```toml
+//! [system.Cargo]
+//! target = "aarch64-unknown-none"
+//! package = "my-kernel"
+//! features = ["feature1", "feature2"]
+//! to_bin = true
+//! ```
+
use std::collections::HashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
+/// Root build configuration structure.
+///
+/// This is the top-level configuration that specifies which build system
+/// to use (Cargo or custom shell commands).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct BuildConfig {
+ /// The build system configuration.
pub system: BuildSystem,
}
+/// Specifies the build system to use.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum BuildSystem {
+ /// Use custom shell commands for building.
Custom(Custom),
+ /// Use Cargo for building.
Cargo(Cargo),
}
+/// Configuration for custom (non-Cargo) build systems.
+///
+/// This allows using arbitrary shell commands for building,
+/// useful for projects that don't use Cargo or need special build steps.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct Custom {
- /// shell command to build the kernel
+ /// Shell command to build the kernel.
pub build_cmd: String,
- /// path to the built ELF file
+ /// Path to the built ELF file.
pub elf_path: String,
- /// whether to output as binary
+ /// Whether to convert the ELF to raw binary format.
pub to_bin: bool,
}
+/// Configuration for Cargo-based builds.
+///
+/// This structure contains all the options needed to configure a Cargo build,
+/// including target architecture, features, environment variables, and build hooks.
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct Cargo {
- /// environment variables
+ /// Environment variables to set during the build.
pub env: HashMap,
- /// target triple
+ /// Target triple (e.g., "aarch64-unknown-none", "riscv64gc-unknown-none-elf").
pub target: String,
- /// package name
+ /// Package name to build.
pub package: String,
- /// features to enable
+ /// Cargo features to enable.
pub features: Vec,
- /// log level feature
+ /// Log level feature to automatically enable.
pub log: Option,
- /// extra cargo .config.toml file
- /// can be url or local path
+ /// Extra Cargo config file path or URL.
+ ///
+ /// Can be a local path or a URL (including GitHub URLs which are
+ /// automatically converted to raw content URLs).
pub extra_config: Option,
- /// other cargo args
+ /// Additional Cargo command-line arguments.
pub args: Vec,
- /// shell commands before build
+ /// Shell commands to run before the build.
pub pre_build_cmds: Vec,
- /// shell commands after build
- /// `KERNEL_ELF` env var is set to the built ELF path
+ /// Shell commands to run after the build.
+ ///
+ /// The `KERNEL_ELF` environment variable is set to the built ELF path.
pub post_build_cmds: Vec,
- /// whether to output as binary
+ /// Whether to convert the ELF to raw binary format after building.
pub to_bin: bool,
}
+/// Dependency configuration for feature management.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct Depend {
+ /// Dependency name.
pub name: String,
+ /// Features to enable for this dependency.
pub d_features: Vec,
}
+/// Log level configuration for the `log` crate.
+///
+/// When specified, automatically enables the corresponding
+/// `log/max_level_*` or `log/release_max_level_*` feature.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum LogLevel {
+ /// Trace level logging.
Trace,
+ /// Debug level logging.
Debug,
+ /// Info level logging.
Info,
+ /// Warning level logging.
Warn,
+ /// Error level logging.
Error,
}
diff --git a/ostool/src/build/mod.rs b/ostool/src/build/mod.rs
index 09c16df..39636c8 100644
--- a/ostool/src/build/mod.rs
+++ b/ostool/src/build/mod.rs
@@ -1,3 +1,23 @@
+//! Build system configuration and Cargo integration.
+//!
+//! This module provides functionality for building operating system projects
+//! using Cargo or custom build commands. It supports:
+//!
+//! - Configuring build options via TOML configuration files
+//! - Running pre-build and post-build shell commands
+//! - Automatic feature detection and configuration
+//! - Multiple runner types (QEMU, U-Boot)
+//!
+//! # Example
+//!
+//! ```rust,no_run
+//! use ostool::build::config::{BuildConfig, BuildSystem, Cargo};
+//! use ostool::ctx::AppContext;
+//!
+//! // Build configurations are typically loaded from TOML files
+//! // See .build.toml for example configuration format
+//! ```
+
use std::path::PathBuf;
use anyhow::Context;
@@ -10,21 +30,43 @@ use crate::{
ctx::AppContext,
};
+/// Cargo builder implementation for building projects.
pub mod cargo_builder;
+
+/// Build configuration types and structures.
pub mod config;
+/// Specifies the type of runner to use after building.
+///
+/// This enum determines how the built artifact will be executed,
+/// either through QEMU emulation or via U-Boot on real hardware.
pub enum CargoRunnerKind {
+ /// Run the built artifact in QEMU emulator.
Qemu {
+ /// Optional path to QEMU configuration file.
qemu_config: Option,
+ /// Whether to enable debug mode (GDB server).
debug: bool,
+ /// Whether to dump the device tree blob.
dtb_dump: bool,
},
+ /// Run the built artifact on real hardware via U-Boot.
Uboot {
+ /// Optional path to U-Boot configuration file.
uboot_config: Option,
},
}
impl AppContext {
+ /// Builds the project using the specified build configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `config` - The build configuration specifying how to build the project.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the build process fails.
pub async fn build_with_config(&mut self, config: &config::BuildConfig) -> anyhow::Result<()> {
match &config.system {
config::BuildSystem::Custom(custom) => self.build_custom(custom)?,
@@ -35,23 +77,65 @@ impl AppContext {
Ok(())
}
+ /// Builds the project from the specified configuration file path.
+ ///
+ /// This is the main entry point for building projects. It loads the
+ /// configuration from the specified path (or default `.build.toml`)
+ /// and executes the build.
+ ///
+ /// # Arguments
+ ///
+ /// * `config_path` - Optional path to the build configuration file.
+ /// Defaults to `.build.toml` in the workspace directory.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the configuration cannot be loaded or the build fails.
pub async fn build(&mut self, config_path: Option) -> anyhow::Result<()> {
let build_config = self.prepare_build_config(config_path, false).await?;
println!("Build configuration: {:?}", build_config);
self.build_with_config(&build_config).await
}
+ /// Executes a custom build using shell commands.
+ ///
+ /// # Arguments
+ ///
+ /// * `config` - Custom build configuration containing the shell command.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the shell command fails.
pub fn build_custom(&mut self, config: &Custom) -> anyhow::Result<()> {
self.shell_run_cmd(&config.build_cmd)?;
Ok(())
}
+ /// Builds the project using Cargo.
+ ///
+ /// # Arguments
+ ///
+ /// * `config` - Cargo build configuration.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the Cargo build fails.
pub async fn cargo_build(&mut self, config: &Cargo) -> anyhow::Result<()> {
cargo_builder::CargoBuilder::build_auto(self, config)
.execute()
.await
}
+ /// Builds and runs the project using Cargo with the specified runner.
+ ///
+ /// # Arguments
+ ///
+ /// * `config` - Cargo build configuration.
+ /// * `runner` - The type of runner to use (QEMU or U-Boot).
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the build or run fails.
pub async fn cargo_run(
&mut self,
config: &Cargo,
diff --git a/ostool/src/ctx.rs b/ostool/src/ctx.rs
index be7f1cd..4dfa6c3 100644
--- a/ostool/src/ctx.rs
+++ b/ostool/src/ctx.rs
@@ -1,3 +1,9 @@
+//! Application context and state management.
+//!
+//! This module provides the [`AppContext`] type which holds the global state
+//! for the ostool application, including paths, build configuration, and
+//! architecture information.
+
use std::{path::PathBuf, sync::Arc};
use anyhow::anyhow;
@@ -15,31 +21,43 @@ use tokio::fs;
use crate::build::config::BuildConfig;
-/// Configuration for output directories (set from external config)
+/// Configuration for output directories.
+///
+/// Specifies where build outputs should be placed.
#[derive(Default, Clone)]
pub struct OutputConfig {
+ /// Custom build directory (overrides default `target/`).
pub build_dir: Option,
+ /// Custom binary output directory.
pub bin_dir: Option,
}
-/// Build artifacts (generated during build)
+/// Build artifacts generated during the build process.
#[derive(Default, Clone)]
pub struct OutputArtifacts {
+ /// Path to the built ELF file.
pub elf: Option,
+ /// Path to the converted binary file.
pub bin: Option,
}
-/// Path configuration grouping all path-related fields
+/// Path configuration grouping all path-related fields.
#[derive(Default, Clone)]
pub struct PathConfig {
+ /// Workspace root directory.
pub workspace: PathBuf,
+ /// Cargo manifest directory.
pub manifest: PathBuf,
+ /// Output directory configuration.
pub config: OutputConfig,
+ /// Generated build artifacts.
pub artifacts: OutputArtifacts,
}
impl PathConfig {
- /// Get build directory, defaulting to manifest/target if not configured
+ /// Gets the build directory.
+ ///
+ /// Returns the configured build directory, or defaults to `manifest/target`.
pub fn build_dir(&self) -> PathBuf {
self.config
.build_dir
@@ -47,22 +65,44 @@ impl PathConfig {
.unwrap_or_else(|| self.manifest.join("target"))
}
- /// Get bin directory, defaulting to build_dir if not configured
+ /// Gets the binary output directory if configured.
pub fn bin_dir(&self) -> Option {
self.config.bin_dir.clone()
}
}
+/// The main application context holding all state.
+///
+/// `AppContext` is the central state container for ostool operations.
+/// It manages paths, build configuration, architecture detection, and
+/// provides methods for building and running OS projects.
#[derive(Default, Clone)]
pub struct AppContext {
+ /// Path configuration for workspace, manifest, and outputs.
pub paths: PathConfig,
+ /// Whether debug mode is enabled.
pub debug: bool,
+ /// Detected CPU architecture from the ELF file.
pub arch: Option,
+ /// Current build configuration.
pub build_config: Option,
+ /// Path to the build configuration file.
pub build_config_path: Option,
}
impl AppContext {
+ /// Executes a shell command in the current context.
+ ///
+ /// The command is run in the manifest directory with the `KERNEL_ELF`
+ /// environment variable set if an ELF artifact is available.
+ ///
+ /// # Arguments
+ ///
+ /// * `cmd` - The shell command to execute.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the command fails to execute.
pub fn shell_run_cmd(&self, cmd: &str) -> anyhow::Result<()> {
let mut command = match std::env::consts::OS {
"windows" => {
@@ -88,6 +128,10 @@ impl AppContext {
Ok(())
}
+ /// Creates a new command builder for the given program.
+ ///
+ /// The command is configured to run in the manifest directory with
+ /// variable substitution support.
pub fn command(&self, program: &str) -> crate::utils::Command {
let this = self.clone();
crate::utils::Command::new(program, &self.paths.manifest, move |s| {
@@ -95,6 +139,11 @@ impl AppContext {
})
}
+ /// Gets the Cargo metadata for the current workspace.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if `cargo metadata` fails.
pub fn metadata(&self) -> anyhow::Result {
let res = cargo_metadata::MetadataCommand::new()
.current_dir(&self.paths.manifest)
@@ -103,6 +152,9 @@ impl AppContext {
Ok(res)
}
+ /// Sets the ELF file path and detects its architecture.
+ ///
+ /// This also reads the ELF file to detect the target CPU architecture.
pub async fn set_elf_path(&mut self, path: PathBuf) {
self.paths.artifacts.elf = Some(path.clone());
let binary_data = match fs::read(path).await {
@@ -122,6 +174,17 @@ impl AppContext {
self.arch = Some(file.architecture())
}
+ /// Strips debug symbols from the ELF file.
+ ///
+ /// Creates a new `.elf` file with debug symbols stripped using `rust-objcopy`.
+ ///
+ /// # Returns
+ ///
+ /// Returns the path to the stripped ELF file.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if no ELF file is set or `rust-objcopy` fails.
pub fn objcopy_elf(&mut self) -> anyhow::Result {
let elf_path = self
.paths
@@ -165,6 +228,18 @@ impl AppContext {
Ok(stripped_elf_path)
}
+ /// Converts the ELF file to raw binary format.
+ ///
+ /// Uses `rust-objcopy` to convert the ELF file to a flat binary file
+ /// suitable for direct loading by bootloaders.
+ ///
+ /// # Returns
+ ///
+ /// Returns the path to the generated binary file.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if no ELF file is set or `rust-objcopy` fails.
pub fn objcopy_output_bin(&mut self) -> anyhow::Result {
if self.paths.artifacts.bin.is_some() {
debug!("BIN file already exists: {:?}", self.paths.artifacts.bin);
@@ -225,6 +300,20 @@ impl AppContext {
Ok(bin_path)
}
+ /// Loads and prepares the build configuration.
+ ///
+ /// This method loads the build configuration from a TOML file. If `menu` is
+ /// true, an interactive TUI is shown for configuration editing.
+ ///
+ /// # Arguments
+ ///
+ /// * `config_path` - Optional path to the configuration file. Defaults to
+ /// `.build.toml` in the workspace directory.
+ /// * `menu` - If true, shows an interactive configuration menu.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the configuration file cannot be loaded or parsed.
pub async fn prepare_build_config(
&mut self,
config_path: Option,
@@ -250,6 +339,10 @@ impl AppContext {
Ok(c)
}
+ /// Replaces variable placeholders in a string.
+ ///
+ /// Currently supports `${workspaceFolder}` which is replaced with the
+ /// workspace directory path.
pub fn value_replace_with_var
(&self, value: S) -> String
where
S: AsRef,
@@ -261,6 +354,9 @@ impl AppContext {
)
}
+ /// Returns UI hooks for the configuration editor.
+ ///
+ /// These hooks provide interactive selection dialogs for features and packages.
pub fn ui_hocks(&self) -> Vec {
vec![self.ui_hock_feature_select(), self.ui_hock_pacage_select()]
}
diff --git a/ostool/src/lib.rs b/ostool/src/lib.rs
index 57ac6e4..d3071f6 100644
--- a/ostool/src/lib.rs
+++ b/ostool/src/lib.rs
@@ -1,10 +1,65 @@
+//! # ostool
+//!
+//! A comprehensive toolkit for operating system development.
+//!
+//! `ostool` provides utilities for building, running, and debugging operating systems,
+//! with special support for embedded systems and bootloader interaction.
+//!
+//! ## Features
+//!
+//! - **Build System**: Cargo-based build configuration with customizable options
+//! - **QEMU Integration**: Easy QEMU launching with various architectures support
+//! - **U-Boot Support**: Serial communication and file transfer via YMODEM
+//! - **TFTP Server**: Built-in TFTP server for network booting
+//! - **Menu Configuration**: TUI-based configuration editor (like Linux kernel's menuconfig)
+//! - **Serial Terminal**: Interactive serial terminal for device communication
+//!
+//! ## Modules
+//!
+//! - [`build`] - Build system configuration and Cargo integration
+//! - [`ctx`] - Application context and state management
+//! - [`menuconfig`] - TUI-based menu configuration
+//! - [`run`] - QEMU, TFTP, and U-Boot runners
+//! - [`sterm`] - Serial terminal implementation
+//! - [`utils`] - Common utilities and helper functions
+//!
+//! ## Example
+//!
+//! ```rust,no_run
+//! // ostool is primarily used as a CLI tool
+//! // See the binary targets for usage examples
+//! ```
+
#![cfg(not(target_os = "none"))]
+/// Build system configuration and Cargo integration.
+///
+/// Provides functionality for configuring and executing Cargo builds
+/// with custom options and target specifications.
pub mod build;
+
+/// Application context and state management.
pub mod ctx;
+
+/// TUI-based menu configuration system.
+///
+/// Similar to Linux kernel's menuconfig, allows users to configure
+/// build options through an interactive terminal interface.
pub mod menuconfig;
+
+/// Runtime execution modules for QEMU, TFTP, and U-Boot.
+///
+/// Contains implementations for launching QEMU instances,
+/// running TFTP servers, and communicating with U-Boot.
pub mod run;
+
+/// Serial terminal implementation.
+///
+/// Provides an interactive serial terminal for communication
+/// with embedded devices and development boards.
pub mod sterm;
+
+/// Common utilities and helper functions.
pub mod utils;
#[macro_use]
diff --git a/ostool/src/menuconfig.rs b/ostool/src/menuconfig.rs
index 5de237c..6ec7fbf 100644
--- a/ostool/src/menuconfig.rs
+++ b/ostool/src/menuconfig.rs
@@ -1,3 +1,13 @@
+//! TUI-based menu configuration system.
+//!
+//! This module provides an interactive terminal user interface for configuring
+//! build options, similar to Linux kernel's menuconfig. It supports editing
+//! configuration for:
+//!
+//! - Build settings (`.build.toml`)
+//! - QEMU settings (`.qemu.toml`)
+//! - U-Boot settings (`.uboot.toml`)
+
use anyhow::Result;
use clap::ValueEnum;
use log::info;
@@ -7,15 +17,30 @@ use crate::ctx::AppContext;
use crate::run::qemu::QemuConfig;
use crate::run::uboot::UbootConfig;
+/// Menu configuration mode selector.
#[derive(ValueEnum, Clone, Debug)]
pub enum MenuConfigMode {
+ /// Configure QEMU runner settings.
Qemu,
+ /// Configure U-Boot runner settings.
Uboot,
}
+/// Handler for menu configuration operations.
pub struct MenuConfigHandler;
impl MenuConfigHandler {
+ /// Handles the menu configuration command.
+ ///
+ /// # Arguments
+ ///
+ /// * `ctx` - The application context.
+ /// * `mode` - Optional mode specifying which configuration to edit.
+ /// If `None`, shows the default build configuration menu.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the configuration cannot be loaded or saved.
pub async fn handle_menuconfig(
ctx: &mut AppContext,
mode: Option,
diff --git a/ostool/src/run/mod.rs b/ostool/src/run/mod.rs
index 41aefae..65260fc 100644
--- a/ostool/src/run/mod.rs
+++ b/ostool/src/run/mod.rs
@@ -1,5 +1,20 @@
+//! Runtime execution modules for QEMU, TFTP, and U-Boot.
+//!
+//! This module contains implementations for running operating systems
+//! in various environments:
+//!
+//! - [`qemu`] - Running in QEMU emulator with UEFI support
+//! - [`tftp`] - TFTP server for network booting
+//! - [`uboot`] - U-Boot bootloader integration via serial/YMODEM
+
+/// QEMU emulator runner with UEFI/OVMF support.
pub mod qemu;
+
+/// TFTP server for network booting.
pub mod tftp;
+
+/// U-Boot bootloader integration.
pub mod uboot;
+/// OVMF prebuilt firmware downloader (internal).
mod ovmf_prebuilt;
diff --git a/ostool/src/run/qemu.rs b/ostool/src/run/qemu.rs
index 0c674c9..aa7619a 100644
--- a/ostool/src/run/qemu.rs
+++ b/ostool/src/run/qemu.rs
@@ -1,3 +1,25 @@
+//! QEMU emulator runner with UEFI/OVMF support.
+//!
+//! This module provides functionality for running operating systems in QEMU
+//! with support for:
+//!
+//! - Multiple architectures (x86_64, aarch64, riscv64, etc.)
+//! - UEFI boot via OVMF firmware
+//! - Debug mode with GDB server
+//! - Output pattern matching for test automation
+//!
+//! # Configuration
+//!
+//! QEMU configuration is stored in `.qemu.toml` files:
+//!
+//! ```toml
+//! args = ["-nographic", "-cpu", "cortex-a53"]
+//! uefi = false
+//! to_bin = true
+//! success_regex = ["All tests passed"]
+//! fail_regex = ["PANIC", "FAILED"]
+//! ```
+
use std::{
ffi::OsString,
io::{BufReader, Read},
@@ -19,23 +41,47 @@ use crate::{
run::ovmf_prebuilt::{Arch, FileType, Prebuilt, Source},
};
+/// QEMU configuration structure.
+///
+/// This configuration is typically loaded from a `.qemu.toml` file.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
pub struct QemuConfig {
+ /// Additional QEMU command-line arguments.
pub args: Vec,
+ /// Whether to use UEFI boot via OVMF firmware.
pub uefi: bool,
- /// objcopy output as binary
+ /// Whether to convert ELF to raw binary before loading.
pub to_bin: bool,
+ /// Regex patterns that indicate successful execution.
pub success_regex: Vec,
+ /// Regex patterns that indicate failed execution.
pub fail_regex: Vec,
}
+/// Arguments for running QEMU.
#[derive(Debug, Clone)]
pub struct RunQemuArgs {
+ /// Optional path to QEMU configuration file.
pub qemu_config: Option,
+ /// Whether to dump the device tree blob.
pub dtb_dump: bool,
+ /// Whether to show QEMU output.
pub show_output: bool,
}
+/// Runs the operating system in QEMU.
+///
+/// This function configures and launches QEMU with the appropriate settings
+/// based on the detected architecture and configuration file.
+///
+/// # Arguments
+///
+/// * `ctx` - The application context containing paths and build artifacts.
+/// * `args` - QEMU run arguments.
+///
+/// # Errors
+///
+/// Returns an error if QEMU fails to start or exits with an error.
pub async fn run_qemu(ctx: AppContext, args: RunQemuArgs) -> anyhow::Result<()> {
// Build logic will be implemented here
let config_path = match args.qemu_config.clone() {
diff --git a/ostool/src/run/tftp.rs b/ostool/src/run/tftp.rs
index 23719d6..d445dca 100644
--- a/ostool/src/run/tftp.rs
+++ b/ostool/src/run/tftp.rs
@@ -1,3 +1,17 @@
+//! TFTP server for network booting.
+//!
+//! This module provides a simple TFTP server for network booting scenarios,
+//! typically used with U-Boot to transfer kernel images over the network.
+//!
+//! # Permissions
+//!
+//! The TFTP server binds to port 69, which requires elevated privileges.
+//! On Linux, you can grant the necessary capabilities with:
+//!
+//! ```bash
+//! sudo setcap cap_net_bind_service=+eip $(which ostool)
+//! ```
+
use std::net::{IpAddr, Ipv4Addr};
use colored::Colorize as _;
@@ -5,6 +19,19 @@ use tftpd::{Config, Server};
use crate::ctx::AppContext;
+/// Starts a TFTP server serving files from the build output directory.
+///
+/// The server runs in a background thread and serves files from the directory
+/// containing the ELF/binary artifacts.
+///
+/// # Arguments
+///
+/// * `app` - The application context containing the file paths.
+///
+/// # Errors
+///
+/// Returns an error if the server fails to start (e.g., port already in use
+/// or insufficient permissions).
pub fn run_tftp_server(app: &AppContext) -> anyhow::Result<()> {
// TFTP server implementation goes here
let mut file_dir = app.paths.manifest.clone();
diff --git a/ostool/src/sterm/mod.rs b/ostool/src/sterm/mod.rs
index 7cd10e0..eb7c1da 100644
--- a/ostool/src/sterm/mod.rs
+++ b/ostool/src/sterm/mod.rs
@@ -1,3 +1,16 @@
+//! Serial terminal implementation.
+//!
+//! This module provides an interactive serial terminal for communication
+//! with embedded devices and development boards. It supports:
+//!
+//! - Full keyboard input with special key sequences
+//! - Line-based output callback for pattern matching
+//! - Raw terminal mode for proper character handling
+//!
+//! # Exit Sequence
+//!
+//! Press `Ctrl+A` followed by `x` to exit the serial terminal.
+
use std::io::{self, Read, Write};
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
@@ -15,22 +28,43 @@ type Tx = Box;
type Rx = Box;
type OnlineCallback = Box;
+/// Interactive serial terminal.
+///
+/// `SerialTerm` provides a bidirectional terminal interface over serial ports,
+/// allowing users to interact with embedded devices in real-time.
+///
+/// # Example
+///
+/// ```rust,no_run
+/// use ostool::sterm::SerialTerm;
+///
+/// // SerialTerm is typically used with serial port connections
+/// // See run::uboot for usage examples
+/// ```
pub struct SerialTerm {
tx: Arc>,
rx: Arc>,
on_line: Option,
}
+/// Handle for controlling the terminal session.
+///
+/// Provides methods to stop the terminal from within callbacks.
pub struct TermHandle {
is_running: AtomicBool,
}
impl TermHandle {
+ /// Stops the terminal session.
+ ///
+ /// This can be called from within a line callback to terminate the session
+ /// when a specific pattern is detected.
pub fn stop(&self) {
self.is_running
.store(false, std::sync::atomic::Ordering::Release);
}
+ /// Returns whether the terminal session is still running.
pub fn is_running(&self) -> bool {
self.is_running.load(std::sync::atomic::Ordering::Acquire)
}
@@ -44,6 +78,13 @@ enum KeySequenceState {
}
impl SerialTerm {
+ /// Creates a new serial terminal with the given read/write streams.
+ ///
+ /// # Arguments
+ ///
+ /// * `tx` - Writer for sending data to the serial port.
+ /// * `rx` - Reader for receiving data from the serial port.
+ /// * `on_line` - Callback function invoked for each complete line received.
pub fn new(tx: Tx, rx: Rx, on_line: F) -> Self
where
F: Fn(&TermHandle, &str) + Send + Sync + 'static,
@@ -55,6 +96,14 @@ impl SerialTerm {
}
}
+ /// Runs the interactive serial terminal.
+ ///
+ /// This method blocks until the user exits (Ctrl+A x) or the line callback
+ /// calls `TermHandle::stop()`.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if terminal mode cannot be set or I/O fails.
pub async fn run(&mut self) -> anyhow::Result<()> {
// 启用raw模式
diff --git a/ostool/src/utils.rs b/ostool/src/utils.rs
index 2798f21..7fddb9b 100644
--- a/ostool/src/utils.rs
+++ b/ostool/src/utils.rs
@@ -1,3 +1,8 @@
+//! Common utilities and helper functions.
+//!
+//! This module provides utility types and functions used throughout ostool,
+//! including command execution helpers and string processing utilities.
+
use std::{
ffi::OsStr,
ops::{Deref, DerefMut},
@@ -7,6 +12,10 @@ use std::{
use anyhow::bail;
use colored::Colorize;
+/// A command builder wrapper with variable substitution support.
+///
+/// `Command` wraps `std::process::Command` and adds support for automatic
+/// variable replacement in arguments and environment values.
pub struct Command {
inner: std::process::Command,
value_replace: Box String>,
@@ -27,6 +36,13 @@ impl DerefMut for Command {
}
impl Command {
+ /// Creates a new command builder.
+ ///
+ /// # Arguments
+ ///
+ /// * `program` - The program to execute.
+ /// * `workdir` - The working directory for the command.
+ /// * `value_replace` - Function to perform variable substitution on arguments.
pub fn new(
program: S,
workdir: &Path,
@@ -45,6 +61,7 @@ impl Command {
}
}
+ /// Prints the command to stdout with colored formatting.
pub fn print_cmd(&self) {
let mut cmd_str = self.get_program().to_string_lossy().to_string();
@@ -56,6 +73,11 @@ impl Command {
println!("{}", cmd_str.purple().bold());
}
+ /// Executes the command and waits for it to complete.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the command fails to execute or exits with non-zero status.
pub fn run(&mut self) -> anyhow::Result<()> {
self.print_cmd();
let status = self.status()?;
@@ -65,6 +87,7 @@ impl Command {
Ok(())
}
+ /// Adds an argument to the command with variable substitution.
pub fn arg(&mut self, arg: S) -> &mut Command
where
S: AsRef,
@@ -74,6 +97,7 @@ impl Command {
self
}
+ /// Adds multiple arguments to the command with variable substitution.
pub fn args(&mut self, args: I) -> &mut Command
where
I: IntoIterator- ,
@@ -85,6 +109,7 @@ impl Command {
self
}
+ /// Sets an environment variable for the command with variable substitution.
pub fn env(&mut self, key: K, val: V) -> &mut Command
where
K: AsRef,
@@ -144,6 +169,21 @@ impl Command {
// Ok(config)
// }
+/// Replaces environment variable placeholders in a string.
+///
+/// Placeholders use the format `${env:VAR_NAME}` where `VAR_NAME` is the
+/// name of an environment variable. If the variable is not set, the
+/// placeholder is replaced with an empty string.
+///
+/// # Example
+///
+/// ```rust
+/// use ostool::utils::replace_env_placeholders;
+///
+/// unsafe { std::env::set_var("MY_VAR", "hello"); }
+/// let result = replace_env_placeholders("Value: ${env:MY_VAR}").unwrap();
+/// assert_eq!(result, "Value: hello");
+/// ```
pub fn replace_env_placeholders(input: &str) -> anyhow::Result {
use std::env;
diff --git a/uboot-shell/Cargo.toml b/uboot-shell/Cargo.toml
index ae86954..d7acb92 100644
--- a/uboot-shell/Cargo.toml
+++ b/uboot-shell/Cargo.toml
@@ -2,12 +2,14 @@
authors = ["周睿 "]
categories = ["os", "embedded", "development-tools"]
description = "A crate for communicating with u-boot"
+documentation = "https://docs.rs/uboot-shell"
edition = "2024"
-keywords = ["u-boot", "shell", "embedded"]
+keywords = ["u-boot", "shell", "embedded", "serial", "ymodem"]
license = "MIT"
name = "uboot-shell"
-repository = "https://github.com/ZR233/ostool/ostool"
-version = "0.2.0"
+readme = "README.md"
+repository = "https://github.com/ZR233/ostool"
+version = "0.2.1"
[dependencies]
colored = "3"
diff --git a/uboot-shell/README.md b/uboot-shell/README.md
index b527ff3..bef3423 100644
--- a/uboot-shell/README.md
+++ b/uboot-shell/README.md
@@ -5,6 +5,8 @@ A crate for communicating with u-boot.
## Usage
```rust
+use uboot_shell::UbootShell;
+
let port = "/dev/ttyUSB0";
let baud = 115200;
let rx = serialport::new(port, baud)
@@ -12,7 +14,7 @@ let rx = serialport::new(port, baud)
.unwrap();
let tx = rx.try_clone().unwrap();
println!("wait for u-boot shell...");
-let mut uboot = UbootShell::new(tx, rx);
+let mut uboot = UbootShell::new(tx, rx).unwrap();
println!("u-boot shell ready");
let res = uboot.cmd("help").unwrap();
println!("{}", res);
diff --git a/uboot-shell/src/crc.rs b/uboot-shell/src/crc.rs
index 0e5a092..ccb5251 100644
--- a/uboot-shell/src/crc.rs
+++ b/uboot-shell/src/crc.rs
@@ -1,4 +1,9 @@
-/* Table of CRC constants - implements x^16+x^12+x^5+1 */
+//! CRC16-CCITT checksum implementation.
+//!
+//! This module provides CRC16-CCITT checksum calculation used by the YMODEM protocol.
+//! The polynomial used is x^16 + x^12 + x^5 + 1 (0x1021).
+
+/// CRC16-CCITT lookup table - implements polynomial x^16+x^12+x^5+1
const CRC16_TAB: &[u16] = &[
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b,
0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
@@ -24,6 +29,25 @@ const CRC16_TAB: &[u16] = &[
0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
];
+/// Calculates CRC16-CCITT checksum for the given data.
+///
+/// # Arguments
+///
+/// * `cksum` - Initial checksum value (usually 0)
+/// * `buf` - Data buffer to calculate checksum for
+///
+/// # Returns
+///
+/// The calculated CRC16-CCITT checksum value.
+///
+/// # Example
+///
+/// ```rust
+/// use uboot_shell::crc::crc16_ccitt;
+///
+/// let data = b"Hello, World!";
+/// let checksum = crc16_ccitt(0, data);
+/// ```
pub fn crc16_ccitt(mut cksum: u16, buf: &[u8]) -> u16 {
for &byte in buf {
cksum = CRC16_TAB[((cksum >> 8) ^ byte as u16) as usize] ^ (cksum << 8);
diff --git a/uboot-shell/src/lib.rs b/uboot-shell/src/lib.rs
index f636a45..faf23f3 100644
--- a/uboot-shell/src/lib.rs
+++ b/uboot-shell/src/lib.rs
@@ -1,3 +1,53 @@
+//! # uboot-shell
+//!
+//! A Rust library for communicating with U-Boot bootloader over serial connection.
+//!
+//! This crate provides functionality to interact with U-Boot shell, execute commands,
+//! transfer files via YMODEM protocol, and manage environment variables.
+//!
+//! ## Features
+//!
+//! - Automatic U-Boot shell detection and synchronization
+//! - Command execution with retry support
+//! - YMODEM file transfer protocol implementation
+//! - Environment variable management
+//! - CRC16-CCITT checksum support
+//!
+//! ## Quick Start
+//!
+//! ```rust,no_run
+//! use uboot_shell::UbootShell;
+//! use std::io::{Read, Write};
+//!
+//! // Open serial port (using serialport crate)
+//! let port = serialport::new("/dev/ttyUSB0", 115200)
+//! .open()
+//! .unwrap();
+//! let rx = port.try_clone().unwrap();
+//! let tx = port;
+//!
+//! // Create U-Boot shell instance (blocks until shell is ready)
+//! let mut uboot = UbootShell::new(tx, rx).unwrap();
+//!
+//! // Execute commands
+//! let output = uboot.cmd("help").unwrap();
+//! println!("{}", output);
+//!
+//! // Get/set environment variables
+//! let bootargs = uboot.env("bootargs").unwrap();
+//! uboot.set_env("myvar", "myvalue").unwrap();
+//!
+//! // Transfer file via YMODEM
+//! uboot.loady(0x80000000, "kernel.bin", |sent, total| {
+//! println!("Progress: {}/{}", sent, total);
+//! }).unwrap();
+//! ```
+//!
+//! ## Modules
+//!
+//! - [`crc`] - CRC16-CCITT checksum implementation
+//! - [`ymodem`] - YMODEM file transfer protocol
+
#[macro_use]
extern crate log;
@@ -13,8 +63,11 @@ use std::{
time::{Duration, Instant},
};
-mod crc;
-mod ymodem;
+/// CRC16-CCITT checksum implementation.
+pub mod crc;
+
+/// YMODEM file transfer protocol implementation.
+pub mod ymodem;
macro_rules! dbg {
($($arg:tt)*) => {{
@@ -26,14 +79,62 @@ const CTRL_C: u8 = 0x03;
const INT_STR: &str = "";
const INT: &[u8] = INT_STR.as_bytes();
+/// U-Boot shell communication interface.
+///
+/// `UbootShell` provides methods to interact with U-Boot bootloader
+/// over a serial connection. It handles shell synchronization,
+/// command execution, and file transfers.
+///
+/// # Example
+///
+/// ```rust,no_run
+/// use uboot_shell::UbootShell;
+///
+/// // Assuming tx and rx are Read/Write implementations
+/// # fn example(tx: impl std::io::Write + Send + 'static, rx: impl std::io::Read + Send + 'static) {
+/// let mut shell = UbootShell::new(tx, rx).unwrap();
+/// let result = shell.cmd("printenv").unwrap();
+/// # }
+/// ```
pub struct UbootShell {
+ /// Transmit channel for sending data to U-Boot.
pub tx: Option>,
+ /// Receive channel for reading data from U-Boot.
pub rx: Option>,
+ /// Shell prompt prefix detected during initialization.
perfix: String,
}
impl UbootShell {
- /// Create a new UbootShell instance, block wait for uboot shell.
+ /// Creates a new UbootShell instance and waits for U-Boot shell to be ready.
+ ///
+ /// This function will block until it successfully detects the U-Boot shell prompt.
+ /// It sends interrupt signals (Ctrl+C) to ensure the shell is in a clean state.
+ ///
+ /// # Arguments
+ ///
+ /// * `tx` - A writable stream for sending data to U-Boot
+ /// * `rx` - A readable stream for receiving data from U-Boot
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(UbootShell)` if the shell is successfully initialized,
+ /// or an `Err` if communication fails.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the serial I/O fails or the prompt cannot be detected
+ /// within the internal retry loop.
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// use uboot_shell::UbootShell;
+ ///
+ /// let port = serialport::new("/dev/ttyUSB0", 115200).open().unwrap();
+ /// let rx = port.try_clone().unwrap();
+ /// let mut uboot = UbootShell::new(port, rx).unwrap();
+ /// ```
pub fn new(tx: impl Write + Send + 'static, rx: impl Read + Send + 'static) -> Result {
let mut s = Self {
tx: Some(Box::new(tx)),
@@ -143,6 +244,21 @@ impl UbootShell {
}
}
+ /// Waits for a specific string to appear in the U-Boot output.
+ ///
+ /// Reads from the serial connection until the specified string is found.
+ ///
+ /// # Arguments
+ ///
+ /// * `val` - The string to wait for
+ ///
+ /// # Returns
+ ///
+ /// Returns the accumulated output up to and including the matched string.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error when the underlying read operation times out or fails.
pub fn wait_for_reply(&mut self, val: &str) -> Result {
let mut reply = Vec::new();
let mut display = Vec::new();
@@ -167,6 +283,18 @@ impl UbootShell {
.to_string())
}
+ /// Sends a command to U-Boot without waiting for the response.
+ ///
+ /// This is useful for commands that don't produce output or when
+ /// you want to handle the response separately.
+ ///
+ /// # Arguments
+ ///
+ /// * `cmd` - The command string to send
+ ///
+ /// # Errors
+ ///
+ /// Returns any I/O error that occurs while writing to the serial stream.
pub fn cmd_without_reply(&mut self, cmd: &str) -> Result<()> {
self.tx().write_all(cmd.as_bytes())?;
self.tx().write_all("\n".as_bytes())?;
@@ -204,6 +332,34 @@ impl UbootShell {
}
}
+ /// Executes a command in U-Boot shell and returns the output.
+ ///
+ /// This method sends the command, waits for completion, and verifies
+ /// that the command executed successfully. It includes automatic retry
+ /// logic (up to 3 attempts) for improved reliability.
+ ///
+ /// # Arguments
+ ///
+ /// * `cmd` - The command string to execute
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(String)` with the command output on success,
+ /// or an `Err` if the command fails after all retries.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the command fails after retries or if serial I/O fails.
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// # use uboot_shell::UbootShell;
+ /// # fn example(uboot: &mut UbootShell) {
+ /// let output = uboot.cmd("printenv bootargs").unwrap();
+ /// println!("bootargs: {}", output);
+ /// # }
+ /// ```
pub fn cmd(&mut self, cmd: &str) -> Result {
info!("cmd: {cmd}");
let mut retry = 3;
@@ -222,11 +378,56 @@ impl UbootShell {
)))
}
+ /// Sets a U-Boot environment variable.
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the environment variable
+ /// * `value` - The value to set
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// # use uboot_shell::UbootShell;
+ /// # fn example(uboot: &mut UbootShell) {
+ /// uboot.set_env("bootargs", "console=ttyS0,115200").unwrap();
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Returns any error from the underlying command execution.
pub fn set_env(&mut self, name: impl Into, value: impl Into) -> Result<()> {
self.cmd(&format!("setenv {} {}", name.into(), value.into()))?;
Ok(())
}
+ /// Gets the value of a U-Boot environment variable.
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the environment variable
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(String)` with the variable value, or an `Err` if not found.
+ ///
+ /// # Errors
+ ///
+ /// Returns `ErrorKind::NotFound` if the variable is not set or cannot be read.
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// # use uboot_shell::UbootShell;
+ /// # fn example(uboot: &mut UbootShell) {
+ /// let bootargs = uboot.env("bootargs").unwrap();
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Returns `ErrorKind::NotFound` if the variable is not set or cannot be read.
pub fn env(&mut self, name: impl Into) -> Result {
let name = name.into();
let s = self.cmd(&format!("echo ${}", name))?;
@@ -244,6 +445,22 @@ impl UbootShell {
Ok(s)
}
+ /// Gets a U-Boot environment variable as an integer.
+ ///
+ /// Supports both decimal and hexadecimal (0x prefix) formats.
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the environment variable
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(usize)` with the parsed integer value,
+ /// or an `Err` if not found or not a valid number.
+ ///
+ /// # Errors
+ ///
+ /// Returns `ErrorKind::InvalidData` if the value is not a valid integer.
pub fn env_int(&mut self, name: impl Into) -> Result {
let name = name.into();
let line = self.env(&name)?;
@@ -255,6 +472,36 @@ impl UbootShell {
))
}
+ /// Transfers a file to U-Boot memory using YMODEM protocol.
+ ///
+ /// Uses the U-Boot `loady` command to receive files via YMODEM protocol.
+ /// The file will be loaded to the specified memory address.
+ ///
+ /// # Arguments
+ ///
+ /// * `addr` - The memory address where the file will be loaded
+ /// * `file` - Path to the file to transfer
+ /// * `on_progress` - Callback function called with (bytes_sent, total_bytes)
+ ///
+ /// # Returns
+ ///
+ /// Returns `Ok(String)` with the U-Boot response on success.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the file cannot be opened, the path has a non-UTF-8
+ /// file name, or if the serial transfer fails.
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// # use uboot_shell::UbootShell;
+ /// # fn example(uboot: &mut UbootShell) {
+ /// uboot.loady(0x80000000, "kernel.bin", |sent, total| {
+ /// println!("Progress: {}/{} bytes", sent, total);
+ /// }).unwrap();
+ /// # }
+ /// ```
pub fn loady(
&mut self,
addr: usize,
@@ -266,11 +513,14 @@ impl UbootShell {
let mut p = ymodem::Ymodem::new(crc);
let file = file.into();
- let name = file.file_name().unwrap().to_str().unwrap();
+ let name = file
+ .file_name()
+ .and_then(|name| name.to_str())
+ .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "file name must be valid UTF-8"))?;
- let mut file = File::open(&file).unwrap();
+ let mut file = File::open(&file)?;
- let size = file.metadata().unwrap().len() as usize;
+ let size = file.metadata()?.len() as usize;
p.send(self, &mut file, name, size, |p| {
on_progress(p, size);
@@ -291,7 +541,10 @@ impl UbootShell {
}
let res = String::from_utf8_lossy(&reply);
if res.contains("try 'help'") {
- panic!("{}", res);
+ return Err(Error::new(
+ ErrorKind::InvalidData,
+ format!("U-Boot loady failed: {res}"),
+ ));
}
}
}
diff --git a/uboot-shell/src/ymodem.rs b/uboot-shell/src/ymodem.rs
index 1eae1f1..c2bd3b8 100644
--- a/uboot-shell/src/ymodem.rs
+++ b/uboot-shell/src/ymodem.rs
@@ -1,23 +1,57 @@
+//! YMODEM file transfer protocol implementation.
+//!
+//! This module implements the YMODEM protocol for file transfers over serial connections.
+//! YMODEM is commonly used by U-Boot's `loady` command.
+//!
+//! ## Protocol Overview
+//!
+//! YMODEM transfers files in 128 or 1024 byte blocks with CRC16 error checking.
+//! The protocol supports:
+//!
+//! - File name and size transmission in the first block
+//! - Automatic block size selection (128 or 1024 bytes)
+//! - CRC16-CCITT or checksum error detection
+//! - Retry mechanism for failed transmissions
+
use std::io::*;
use crate::crc::crc16_ccitt;
+/// Start of Header - 128 byte block
const SOH: u8 = 0x01;
+/// Start of Text - 1024 byte block
const STX: u8 = 0x02;
+/// End of Transmission
const EOT: u8 = 0x04;
+/// Acknowledge
const ACK: u8 = 0x06;
+/// Negative Acknowledge
const NAK: u8 = 0x15;
-// const CAN: u8 = 0x18;
+// const CAN: u8 = 0x18; // Cancel
+/// End of File padding character
const EOF: u8 = 0x1A;
+/// CRC mode request character
const CRC: u8 = 0x43;
+/// YMODEM protocol handler for file transfers.
+///
+/// Implements the YMODEM protocol for sending files over serial connections.
+/// Supports both CRC16 and checksum modes.
pub struct Ymodem {
+ /// Whether to use CRC16 mode (true) or checksum mode (false)
crc_mode: bool,
+ /// Current block number
blk: u8,
+ /// Number of remaining retry attempts
retries: usize,
}
impl Ymodem {
+ /// Creates a new YMODEM sender.
+ ///
+ /// # Arguments
+ ///
+ /// * `crc_mode` - Whether to start in CRC16 mode (`true`) or checksum mode (`false`)
pub fn new(crc_mode: bool) -> Self {
Self {
crc_mode,
@@ -52,6 +86,19 @@ impl Ymodem {
}
}
+ /// Sends a file over the YMODEM protocol.
+ ///
+ /// # Arguments
+ ///
+ /// * `dev` - The device implementing `Read + Write` (serial stream)
+ /// * `file` - The readable file stream
+ /// * `name` - File name reported to the receiver
+ /// * `size` - File size in bytes
+ /// * `on_progress` - Callback invoked with the total bytes sent so far
+ ///
+ /// # Errors
+ ///
+ /// Returns any I/O error from the underlying device or file stream.
pub fn send(
&mut self,
dev: &mut D,