Skip to main content

Development Environment Setup

Contributing

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

ToolMinimum VersionRecommended VersionDescription
Rust1.78+1.80+ stableInstall stable via rustup
cargoWith RustWith RustEnable cargo-nextest, cargo-clippy
rustfmtWith RustWith RustCode formatting, strictly checked by CI
clippyWith RustWith RustStatic analysis, zero warning tolerance
Node.js20+22 LTSDocumentation site and frontend build
protoc25+27+gRPC interface generation
OpenSSL3.0+3.2+TLS dependency
SQLite3.40+3.45+Kernel storage
CMake3.20+3.28+Some C dependency builds
Git2.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:

ExtensionPurposeRequired
rust-analyzerRust LSP language serverYes
Even Better TOMLTOML file editing (Cargo.toml, eneros.toml)Yes
CodeLLDBRust debuggerYes
cratesDependency version hintsRecommended
Better CommentsComment highlightingOptional
Markdown All in OneDocumentation editingOptional

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:

  1. Open Settings → Plugins → Marketplace, search for Rust and install
  2. Open Settings → Languages & Frameworks → Rust:
    • Toolchain location: ~/.cargo/bin or %USERPROFILE%\.cargo\bin
    • Explicit path to stdlib: Leave blank (auto-detect)
    • Use rustfmt: Check
    • Use external linter: Select Clippy
  3. 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

ObjectConventionExample
Crateeneros-<domain>, kebab-caseeneros-powerflow
Modulesnake_casemod newton_raphson;
TypeUpperCamelCase, nounPowerflowSolver
TraitUpperCamelCase, noun or adjectiveTopologyEngine, Auditable
Functionsnake_case, starts with verbfind_islands, solve_powerflow
Methodsnake_case, starts with verbbus.add_load(...)
ConstantSCREAMING_SNAKE_CASEMAX_HISTORY, DEFAULT_PORT
Static VariableSCREAMING_SNAKE_CASEGLOBAL_CONFIG
Local Variablesnake_casebus_count, total_load
Generic ParameterSingle uppercase letter or UpperCamelCaseT, AgentKind
LifetimeShort lowercase letter'a, 'ctx

Error Handling

  • Use Result<T, EnerOSError>; unwrap is not allowed in production code (except tests)
  • Error types implement thiserror::Error, providing #[error("...")] text
  • Upper-layer errors use anyhow::Result or custom Error enum 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 tokio runtime uniformly; avoid mixing blocking calls into async context
  • Long CPU computations use tokio::task::spawn_blocking
  • Async functions return impl Future<Output = Result<T>> or Pin<Box<dyn Future...>>
  • Do not call std::sync::Mutex’s lock().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 /// ```rust must compile
  • Complex APIs provide # Examples section
/// 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)]
FieldDescription
typeCommit type, see table below
scopeImpact scope, usually the crate name (without eneros- prefix), e.g., powerflow, topology
descriptionShort description, imperative mood, lowercase first letter, no trailing period
bodyDetailed description, each line ≤ 72 characters
footerLinked Issue (Closes #123), breaking change marker (BREAKING CHANGE:)

Commit Types

TypePurposeExample
featNew featurefeat(powerflow): support PV bus reactive power limits
fixBug fixfix(topology): fix island detection miss
docsDocumentation changesdocs(tutorials): add load flow tutorial
styleCode style (no logic impact)style(core): unify 4-space indentation
refactorRefactor (not new feature, not bug fix)refactor(topology): extract graph traversal common method
perfPerformance optimizationperf(timeseries): optimize write path to reduce memory copies
testTest relatedtest(constraint): add voltage violation edge cases
buildBuild system or dependenciesbuild(deny): upgrade cargo-deny to 0.14
ciCI configurationci(workflows): add aarch64 build matrix
choreMisc (no source or test impact)chore: update .gitignore
revertRevert previous commitrevert: 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:

BranchPurposeNamingLifecycle
mainMain branch, always releasablemainPermanent
release/*Release branch, bug fixes onlyrelease/0.47.0Archived after release
feat/*Feature developmentfeat/powerflow-pv-limitsDeleted after merge
fix/*Bug fixfix/123-island-detectionDeleted after merge
docs/*Documentationdocs/tutorial-load-flowDeleted after merge
hotfix/*Emergency fix (for released versions)hotfix/0.47.1-cve-2024-xxxxDeleted 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

See Installation and Build - Common Build Issues. Common errors:

Error MessageCauseSolution
linker 'cc' not foundMissing C compilerUbuntu: sudo apt install build-essential
failed to run custom build command for 'openssl-sys'OpenSSL not foundSet OPENSSL_DIR or install libssl-dev
could not find library 'sqlite3'SQLite dev library missingUbuntu: sudo apt install libsqlite3-dev
linking with 'link.exe' failed (Windows)MSVC missingInstall 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:

  1. Infrastructure PR: Extract common traits, types, and utility functions
  2. Core Implementation PR: Implement main logic, depends on infrastructure PR
  3. Test PR: Add complete test cases
  4. 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 -- --check passes
  • cargo clippy --all-targets -- -D warnings passes
  • cargo nextest run all 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 check passes (no banned licenses or duplicate dependencies)
  • CHANGELOG.md updated (if behavior changes are involved)