Development Environment Setup
This page describes how to set up the EnerOS development environment locally, configure common IDEs and debugging tools, and follow project code standards and Git workflows. EnerOS is a Cargo workspace consisting of 56 Rust crates, with specific requirements for the build environment.
Toolchain
Required Tools
| Tool | Minimum Version | Recommended Version | Description |
|---|---|---|---|
| Rust | 1.78+ | 1.80+ stable | Install stable via rustup |
| cargo | With Rust | With Rust | Enable cargo-nextest, cargo-clippy |
| rustfmt | With Rust | With Rust | Code formatting, strictly checked by CI |
| clippy | With Rust | With Rust | Static analysis, zero warning tolerance |
| Node.js | 20+ | 22 LTS | Documentation site and frontend build |
| protoc | 25+ | 27+ | gRPC interface generation |
| OpenSSL | 3.0+ | 3.2+ | TLS dependency |
| SQLite | 3.40+ | 3.45+ | Kernel storage |
| CMake | 3.20+ | 3.28+ | Some C dependency builds |
| Git | 2.30+ | 2.45+ | Version control |
Installation Commands
# Install Rust stable toolchain
rustup default stable
# Add required components
rustup component add clippy rustfmt rust-src rust-analyzer
# Install cargo subcommands
cargo install cargo-nextest
cargo install cargo-deny
cargo install cargo-llvm-cov
cargo install cargo-audit
cargo install cargo-edit # Provides cargo add / cargo upgrade
cargo install cargo-watch # Auto-recompile/retest on file changes
cargo install cargo-flamegraph # Performance flame graph (Linux)
# Verify
rustc --version
cargo --version
cargo nextest --version
System Dependencies
Ubuntu / Debian
sudo apt update
sudo apt install -y \
build-essential \
pkg-config \
libssl-dev \
libsqlite3-dev \
cmake \
git \
curl \
clang \
libclang-dev \
protobuf-compiler
macOS
xcode-select --install
brew install openssl@3 sqlite pkg-config cmake protobuf
Windows
Windows recommends using the MSVC toolchain. After installing Visual Studio Build Tools:
choco install openssl pkgconfiglite cmake git protobuf -y
See Prerequisites for detailed system dependency installation.
Clone and Build
Clone the Repository
# Clone after forking (recommended)
git clone https://github.com/<your-fork>/EnerOS.git
cd EnerOS
# Configure upstream repository
git remote add upstream https://github.com/Gawg-AI/EnerOS.git
git fetch upstream
First Build
# Debug build (default)
cargo build
# Release build (better performance, slower compilation)
cargo build --release
# Build only a specific crate
cargo build -p eneros-powerflow
# Build a specific binary
cargo build -p enerosctl
# Build crate with features
cargo build -p eneros-audit --features worm
cargo build -p eneros-init --features os
Build artifacts are located in target/debug/ or target/release/. See Installation and Build for full Release build.
Accelerate Build
The EnerOS workspace is large; you can accelerate builds in the following ways:
# ~/.cargo/config.toml
[build]
# Limit parallelism to avoid OOM (4GB RAM machines recommend 2)
jobs = 4
# Enable mold linker (Linux, requires installing mold first)
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
# Domestic mirror to accelerate dependency download
[source.crates-io]
replace-with = "ustc"
[source.ustc]
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"
# Enable sccache to cache compilation results (requires cargo install sccache first)
[build]
rustc-wrapper = "sccache"
# Install sccache
cargo install sccache
# Set cache size (default 10GB)
export SCCACHE_CACHE_SIZE="20G"
sccache --start-server
Using cargo-watch
You can use cargo-watch for auto-recompilation during development:
# Watch file changes and automatically run tests
cargo watch -x "nextest run -p eneros-powerflow"
# Watch changes and auto clippy
cargo watch -x "clippy --all-targets -- -D warnings"
# Watch changes and run + test
cargo watch -x run -x "nextest run"
IDE Configuration
VS Code
Recommended extensions:
| Extension | Purpose | Required |
|---|---|---|
| rust-analyzer | Rust LSP language server | Yes |
| Even Better TOML | TOML file editing (Cargo.toml, eneros.toml) | Yes |
| CodeLLDB | Rust debugger | Yes |
| crates | Dependency version hints | Recommended |
| Better Comments | Comment highlighting | Optional |
| Markdown All in One | Documentation editing | Optional |
Recommended Configuration
Create .vscode/settings.json in the repository root (committed to the repo):
{
"rust-analyzer.linkedProjects": ["./Cargo.toml"],
"rust-analyzer.checkOnSave": true,
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.extraArgs": ["--all-targets", "--", "-D", "warnings"],
"rust-analyzer.cargo.features": "all",
"rust-analyzer.inlayHints.parameterHints.enable": true,
"rust-analyzer.inlayHints.typeHints.enable": true,
"rust-analyzer.inlayHints.lifetimeElisionHints.enable": "always",
"rust-analyzer.rustfmt.overrideCommand": ["rustfmt", "--edition", "2021"],
"editor.formatOnSave": true,
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
},
"[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml",
"editor.formatOnSave": true
},
"files.exclude": {
"**/target": true,
"**/.cargo-lock": false
},
"search.exclude": {
"**/target": true,
"**/Cargo.lock": true
},
"lldb.executable": "rust-lldb"
}
Debug Configuration
Add to .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests",
"cargo": {
"args": ["test", "--no-run", "--lib", "--all-features"],
"filter": {
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug powerflow tests",
"cargo": {
"args": ["test", "--no-run", "-p", "eneros-powerflow"],
"filter": {
"name": "powerflow",
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Run eneros-api",
"cargo": {
"args": ["build", "-p", "eneros-api"]
},
"program": "${workspaceFolder}/target/debug/eneros-api",
"args": ["--config", "eneros.toml"],
"cwd": "${workspaceFolder}"
}
]
}
IntelliJ Rust
IntelliJ IDEA / CLion users can use the Rust plugin:
- Open
Settings → Plugins → Marketplace, search forRustand install - Open
Settings → Languages & Frameworks → Rust:- Toolchain location:
~/.cargo/binor%USERPROFILE%\.cargo\bin - Explicit path to stdlib: Leave blank (auto-detect)
- Use rustfmt: Check
- Use external linter: Select
Clippy
- Toolchain location:
- Open
Settings → Languages & Frameworks → Rust → Cargo:- Auto-update project: Check
- Build on save: Check
CLion has built-in LLDB, allowing you to set breakpoints by clicking next to line numbers and debug directly.
Neovim
Neovim users recommend using nvim-lspconfig to configure rust-analyzer:
-- init.lua
require'lspconfig'.rust_analyzer.setup{
settings = {
["rust-analyzer"] = {
cargo = { features = "all", allFeatures = true },
checkOnSave = {
command = "clippy",
extraArgs = {"--all-targets", "--", "-D", "warnings"}
},
inlayHints = { parameterHints = { enable = true } }
}
}
}
Debug Configuration
Log Output
EnerOS uses tracing for structured log output, controlled via environment variables:
# Global log level
export RUST_LOG=info
# Control log level per crate
export RUST_LOG="eneros_powerflow=debug,eneros_topology=trace,info"
# JSON format output (for log collection)
export ENEROS_LOG_FORMAT=json
# Runtime output
RUST_LOG=debug cargo run -p eneros-api
LLDB / GDB Debugging
# Using rust-lldb (recommended)
cargo build -p eneros-powerflow
rust-lldb --target ./target/debug/eneros-powerflow-test -- --nocapture
# Common LLDB commands:
# (lldb) b powerflow::newton_raphson::solve # Set breakpoint
# (lldb) run # Run
# (lldb) next # Step over
# (lldb) step # Step into
# (lldb) print *bus # Print variable
# (lldb) bt # Print call stack
Performance Analysis
# Flame graph (requires cargo-flamegraph)
cargo flamegraph --bin eneros-api -- --config eneros.toml
# Performance baseline comparison
cargo bench -p eneros-powerflow -- --save-baseline my-change
# After modifying code
cargo bench -p eneros-powerflow -- --baseline my-change
# Memory allocation tracking (Linux, requires dhat)
cargo run -p eneros-powerflow --features dhat --release
Code Standards
Formatting and Lint
# Format (strictly checked by CI)
cargo fmt --all
# Check format (no modification)
cargo fmt --all -- --check
# Lint (strictly checked by CI, zero warning tolerance)
cargo clippy --all-targets -- -D warnings
# Check only a specific crate
cargo clippy -p eneros-powerflow -- -D warnings
# Fix auto-fixable clippy warnings
cargo clippy --fix --all-targets --allow-dirty --allow-no-vcs
Naming Conventions
| Object | Convention | Example |
|---|---|---|
| Crate | eneros-<domain>, kebab-case | eneros-powerflow |
| Module | snake_case | mod newton_raphson; |
| Type | UpperCamelCase, noun | PowerflowSolver |
| Trait | UpperCamelCase, noun or adjective | TopologyEngine, Auditable |
| Function | snake_case, starts with verb | find_islands, solve_powerflow |
| Method | snake_case, starts with verb | bus.add_load(...) |
| Constant | SCREAMING_SNAKE_CASE | MAX_HISTORY, DEFAULT_PORT |
| Static Variable | SCREAMING_SNAKE_CASE | GLOBAL_CONFIG |
| Local Variable | snake_case | bus_count, total_load |
| Generic Parameter | Single uppercase letter or UpperCamelCase | T, AgentKind |
| Lifetime | Short lowercase letter | 'a, 'ctx |
Error Handling
- Use
Result<T, EnerOSError>;unwrapis not allowed in production code (except tests) - Error types implement
thiserror::Error, providing#[error("...")]text - Upper-layer errors use
anyhow::Resultor customErrorenum aggregation ?operator attaches context:map_err(|e| EnerOSError::network("connect", e))
// crates/eneros-powerflow/src/error.rs
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PowerflowError {
#[error("Load flow did not converge, iterations {iterations} exceeded limit {max_iterations}")]
NonConvergence { iterations: usize, max_iterations: usize },
#[error("Bus {bus_id} voltage violation: {voltage} p.u., allowed range [{v_min}, {v_max}]")]
VoltageViolation {
bus_id: u64,
voltage: f64,
v_min: f64,
v_max: f64,
},
#[error("Invalid topology data: {0}")]
InvalidTopology(String),
#[error("Linear algebra computation failed: {0}")]
Linalg(#[from] eneros_linalg::LinalgError),
}
pub type Result<T> = std::result::Result<T, PowerflowError>;
Async Standards
- Use
tokioruntime uniformly; avoid mixing blocking calls into async context - Long CPU computations use
tokio::task::spawn_blocking - Async functions return
impl Future<Output = Result<T>>orPin<Box<dyn Future...>> - Do not call
std::sync::Mutex’slock().unwrap()in async blocks
// Correct: async + spawn_blocking for CPU-intensive tasks
pub async fn solve_powerflow_async(&self) -> Result<PowerflowResult> {
let topology = self.topology.clone();
let result = tokio::task::spawn_blocking(move || {
Self::solve_blocking(&topology)
}).await.map_err(|e| PowerflowError::Linalg(eneros_linalg::LinalgError::Other(e.to_string())))?;
Ok(result)
}
// Wrong: blocking call in async context
pub async fn bad_solve(&self) -> Result<PowerflowResult> {
std::thread::sleep(Duration::from_secs(1)); // Forbidden!
self.solve_blocking()
}
Public API Documentation
- Public APIs must have doc comments
/// - Verified via
cargo doc; doc examples/// ```rustmust compile - Complex APIs provide
# Examplessection
/// Solves the load flow equations for a given topology.
///
/// Uses the Newton-Raphson iterative method, with maximum iterations controlled by `max_iterations`.
/// Convergence is considered when the voltage difference between two consecutive iterations is less than `tolerance`.
///
/// # Arguments
/// - `topology`: Grid topology, must have completed island detection
/// - `max_iterations`: Maximum iteration count, recommended 20-50
/// - `tolerance`: Convergence tolerance, default 1e-8
///
/// # Returns
/// Returns [`PowerflowResult`], containing voltage, phase angle, and branch power for each bus.
///
/// # Errors
/// - [`PowerflowError::NonConvergence`]: Iteration count exceeded without convergence
/// - [`PowerflowError::InvalidTopology`]: Topology data incomplete or contains loops
///
/// # Examples
///
/// ```rust
/// use eneros_powerflow::{NewtonRaphsonSolver, PowerflowResult};
/// use eneros_topology::Topology;
///
/// let topo = Topology::from_ieee_14bus();
/// let solver = NewtonRaphsonSolver::new(20, 1e-8);
/// let result: PowerflowResult = solver.solve(&topo).unwrap();
/// assert!(result.converged);
/// ```
pub fn solve(&self, topology: &Topology) -> Result<PowerflowResult> {
// Implementation
unimplemented!()
}
Commit Message Standards
Conventional Commits
EnerOS follows the Conventional Commits 1.0.0 specification:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
| Field | Description |
|---|---|
type | Commit type, see table below |
scope | Impact scope, usually the crate name (without eneros- prefix), e.g., powerflow, topology |
description | Short description, imperative mood, lowercase first letter, no trailing period |
body | Detailed description, each line ≤ 72 characters |
footer | Linked Issue (Closes #123), breaking change marker (BREAKING CHANGE:) |
Commit Types
| Type | Purpose | Example |
|---|---|---|
feat | New feature | feat(powerflow): support PV bus reactive power limits |
fix | Bug fix | fix(topology): fix island detection miss |
docs | Documentation changes | docs(tutorials): add load flow tutorial |
style | Code style (no logic impact) | style(core): unify 4-space indentation |
refactor | Refactor (not new feature, not bug fix) | refactor(topology): extract graph traversal common method |
perf | Performance optimization | perf(timeseries): optimize write path to reduce memory copies |
test | Test related | test(constraint): add voltage violation edge cases |
build | Build system or dependencies | build(deny): upgrade cargo-deny to 0.14 |
ci | CI configuration | ci(workflows): add aarch64 build matrix |
chore | Misc (no source or test impact) | chore: update .gitignore |
revert | Revert previous commit | revert: revert feat(powerflow): PV reactive power limits |
Complete Example
feat(powerflow): support PV bus reactive power limits
- Add q_min/q_max fields to PvBus, default -inf/+inf for backward compatibility
- NewtonRaphsonSolver auto-converts PV node to PQ node on reactive power violation
- Add bus_type_convertor module to handle node type conversion logic
- Add 5 unit tests covering violation scenarios
Closes #123
fix(topology): fix island detection miss for multi-source nodes
When searching for isolated subgraphs, multi-source node merging was not considered,
causing some industrial distribution network scenarios to mistakenly identify
the same electrical island as two.
Fix: Merge and deduplicate source nodes before BFS traversal.
Closes #456
Validate Commit Messages
# Install commitlint tool
cargo install commitlint
# Validate in commit-msg hook
echo '#!/bin/sh
cargo commitlint --commit-msg-file "$1"' > .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg
Branch Strategy and Git Workflow
Branch Model
EnerOS adopts a simplified Git Flow model:
| Branch | Purpose | Naming | Lifecycle |
|---|---|---|---|
main | Main branch, always releasable | main | Permanent |
release/* | Release branch, bug fixes only | release/0.47.0 | Archived after release |
feat/* | Feature development | feat/powerflow-pv-limits | Deleted after merge |
fix/* | Bug fix | fix/123-island-detection | Deleted after merge |
docs/* | Documentation | docs/tutorial-load-flow | Deleted after merge |
hotfix/* | Emergency fix (for released versions) | hotfix/0.47.1-cve-2024-xxxx | Deleted after merge |
Workflow
main ─────●───●───●───●───●────────────────●─── (merge PR)
\ \ /
└─ feat/foo ──●───●─────●─── PR ──/
\ \ \
commit/test/Review
1. Sync Main Branch
# Always branch based on the latest main
git checkout main
git pull upstream main
git checkout -b feat/my-feature
2. Develop and Commit
# Small step commits, each independently compilable
git add crates/eneros-powerflow/src/solver.rs
git commit -m "feat(powerflow): implement PV to PQ node conversion logic"
git add crates/eneros-powerflow/src/bus_type.rs
git commit -m "feat(powerflow): extract node type converter"
git add crates/eneros-powerflow/tests/pv_limits_test.rs
git commit -m "test(powerflow): add PV reactive power violation tests"
3. Maintain Linear History
# Periodically rebase to latest main
git fetch upstream
git rebase upstream/main
# Continue after resolving conflicts
git add .
git rebase --continue
# Force push to personal fork (--force-with-lease is safer)
git push origin feat/my-feature --force-with-lease
4. Handle Review Feedback
# Reviewer suggests changes, after modifying:
git add .
git commit --fixup <commit-sha> # Create fixup commit
git rebase -i --autosquash upstream/main
# Or directly modify the last commit
git add .
git commit --amend --no-edit
git push origin feat/my-feature --force-with-lease
Handling Large PRs
Keep individual PRs under 800 lines; split complex changes into multiple PRs:
# Split strategy: merge infrastructure PR first, then feature PR
# PR #1: refactor(powerflow): extract node type converter
# PR #2: feat(powerflow): support PV bus reactive power limits (depends on #1)
# PR #3: docs(powerflow): add PV node tutorial (depends on #2)
Mark dependencies in the PR description: Depends on: #123.
FAQ
Link Errors
See Installation and Build - Common Build Issues. Common errors:
| Error Message | Cause | Solution |
|---|---|---|
linker 'cc' not found | Missing C compiler | Ubuntu: sudo apt install build-essential |
failed to run custom build command for 'openssl-sys' | OpenSSL not found | Set OPENSSL_DIR or install libssl-dev |
could not find library 'sqlite3' | SQLite dev library missing | Ubuntu: sudo apt install libsqlite3-dev |
linking with 'link.exe' failed (Windows) | MSVC missing | Install Visual Studio Build Tools |
Protobuf Version Mismatch
# Check version, must be ≥ 25
protoc --version
# libprotoc 27.0
# Upgrade
sudo apt install -y protobuf-compiler # Ubuntu
brew upgrade protobuf # macOS
choco upgrade protobuf # Windows
Large PR Splitting
Keep individual PRs under 800 lines; split complex changes into multiple:
- Infrastructure PR: Extract common traits, types, and utility functions
- Core Implementation PR: Implement main logic, depends on infrastructure PR
- Test PR: Add complete test cases
- Documentation PR: Update API references, tutorials, and CHANGELOG
Each PR marks dependencies in its description and merges in order.
cargo build Out of Memory
# Limit parallelism
export CARGO_BUILD_JOBS=2
cargo build
# Or write to config
# ~/.cargo/config.toml
[build]
jobs = 2
Development Checklist
Before submitting each PR, please check:
-
cargo fmt --all -- --checkpasses -
cargo clippy --all-targets -- -D warningspasses -
cargo nextest runall pass -
cargo nextest run -p <my-crate>all pass - Public API changes have updated
cargo doc - Commit messages follow Conventional Commits
- No sensitive content such as keys, certificates, or temporary files
- No sensitive files such as
.env,*.pem,*.key -
cargo deny checkpasses (no banned licenses or duplicate dependencies) -
CHANGELOG.mdupdated (if behavior changes are involved)