跳到主内容

开发环境搭建

贡献指南

开发环境搭建

本页说明如何在本地搭建 EnerOS 开发环境,配置常用 IDE 与调试工具,并遵守项目代码规范与 Git 工作流。EnerOS 是由 56 个 Rust crate 组成的 Cargo workspace,对构建环境有明确要求。

工具链

必备工具

工具最低版本推荐版本说明
Rust1.78+1.80+ stable通过 rustup 安装 stable
cargo随 Rust随 Rust启用 cargo-nextestcargo-clippy
rustfmt随 Rust随 Rust代码格式化,CI 强制校验
clippy随 Rust随 Rust静态检查,零警告容忍
Node.js20+22 LTS文档站点与前端构建
protoc25+27+gRPC 接口生成
OpenSSL3.0+3.2+TLS 依赖
SQLite3.40+3.45+内核存储
CMake3.20+3.28+部分 C 依赖构建
Git2.30+2.45+版本控制

安装命令

# 安装 Rust 稳定版工具链
rustup default stable

# 添加必备组件
rustup component add clippy rustfmt rust-src rust-analyzer

# 安装 cargo 子命令
cargo install cargo-nextest
cargo install cargo-deny
cargo install cargo-llvm-cov
cargo install cargo-audit
cargo install cargo-edit        # 提供 cargo add / cargo upgrade
cargo install cargo-watch       # 文件变化时自动重编译/重测试
cargo install cargo-flamegraph  # 性能火焰图(Linux)

# 验证
rustc --version
cargo --version
cargo nextest --version

系统依赖

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 推荐使用 MSVC 工具链,安装 Visual Studio Build Tools 后:

choco install openssl pkgconfiglite cmake git protobuf -y

详细系统依赖安装见 环境准备

克隆与构建

克隆仓库

# Fork 后克隆(推荐)
git clone https://github.com/<your-fork>/EnerOS.git
cd EnerOS

# 配置上游仓库
git remote add upstream https://github.com/Gawg-AI/EnerOS.git
git fetch upstream

首次构建

# Debug 构建(默认)
cargo build

# Release 构建(性能更优,编译更慢)
cargo build --release

# 仅构建特定 crate
cargo build -p eneros-powerflow

# 构建特定二进制
cargo build -p enerosctl

# 构建带特性的 crate
cargo build -p eneros-audit --features worm
cargo build -p eneros-init --features os

构建产物位于 target/debug/target/release/。完整 Release 构建见 安装与构建

加速构建

EnerOS workspace 较大,可通过以下方式加速构建:

# ~/.cargo/config.toml
[build]
# 限制并行度避免 OOM(4GB 内存机器建议 2)
jobs = 4

# 启用 mold 链接器(Linux,需先安装 mold)
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

# 国内镜像源加速依赖下载
[source.crates-io]
replace-with = "ustc"

[source.ustc]
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"

# 启用 sccache 缓存编译结果(需先 cargo install sccache)
[build]
rustc-wrapper = "sccache"
# 安装 sccache
cargo install sccache

# 设置缓存大小(默认 10GB)
export SCCACHE_CACHE_SIZE="20G"
sccache --start-server

使用 cargo-watch

开发时可用 cargo-watch 自动重编译:

# 监听文件变化并自动运行测试
cargo watch -x "nextest run -p eneros-powerflow"

# 监听变化并自动 clippy
cargo watch -x "clippy --all-targets -- -D warnings"

# 监听变化并运行 + 测试
cargo watch -x run -x "nextest run"

IDE 配置

VS Code

推荐安装以下扩展:

扩展用途必需
rust-analyzerRust LSP 语言服务器
Even Better TOMLTOML 文件编辑(Cargo.toml、eneros.toml)
CodeLLDBRust 调试器
crates依赖版本提示推荐
Better Comments注释高亮可选
Markdown All in One文档编辑可选

推荐配置

在仓库根目录创建 .vscode/settings.json(已提交到仓库):

{
    "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"
}

调试配置

.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 用户可使用 Rust 插件:

  1. 打开 Settings → Plugins → Marketplace,搜索 Rust 并安装
  2. 打开 Settings → Languages & Frameworks → Rust
    • Toolchain location~/.cargo/bin%USERPROFILE%\.cargo\bin
    • Explicit path to stdlib:留空(自动检测)
    • Use rustfmt:勾选
    • Use external linter:选择 Clippy
  3. 打开 Settings → Languages & Frameworks → Rust → Cargo
    • Auto-update project:勾选
    • Build on save:勾选

CLion 由于内置 LLDB,可直接在代码行号旁点击设置断点并调试。

Neovim

Neovim 用户推荐使用 nvim-lspconfig 配置 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 } }
        }
    }
}

调试配置

日志输出

EnerOS 使用 tracing 进行结构化日志输出,可通过环境变量控制日志级别:

# 全局日志级别
export RUST_LOG=info

# 按 crate 控制日志级别
export RUST_LOG="eneros_powerflow=debug,eneros_topology=trace,info"

# JSON 格式输出(用于日志采集)
export ENEROS_LOG_FORMAT=json

# 运行时输出
RUST_LOG=debug cargo run -p eneros-api

LLDB / GDB 调试

# 使用 rust-lldb(推荐)
cargo build -p eneros-powerflow
rust-lldb --target ./target/debug/eneros-powerflow-test -- --nocapture

# 常用 LLDB 命令:
# (lldb) b powerflow::newton_raphson::solve    # 设置断点
# (lldb) run                                   # 运行
# (lldb) next                                  # 单步跳过
# (lldb) step                                  # 单步进入
# (lldb) print *bus                            # 打印变量
# (lldb) bt                                    # 打印调用栈

性能分析

# 火焰图(需安装 cargo-flamegraph)
cargo flamegraph --bin eneros-api -- --config eneros.toml

# 性能基准对比
cargo bench -p eneros-powerflow -- --save-baseline my-change
# 修改代码后
cargo bench -p eneros-powerflow -- --baseline my-change

# 内存分配追踪(Linux,需 dhat)
cargo run -p eneros-powerflow --features dhat --release

代码规范

格式化与 Lint

# 格式化(CI 强制校验)
cargo fmt --all

# 检查格式(不修改)
cargo fmt --all -- --check

# Lint(CI 强制校验,零警告容忍)
cargo clippy --all-targets -- -D warnings

# 仅检查特定 crate
cargo clippy -p eneros-powerflow -- -D warnings

# 修复自动可修复的 clippy 警告
cargo clippy --fix --all-targets --allow-dirty --allow-no-vcs

命名规范

对象规范示例
Crateeneros-<domain>,kebab-caseeneros-powerflow
模块snake_casemod newton_raphson;
类型UpperCamelCase,名词PowerflowSolver
TraitUpperCamelCase,名词或形容词TopologyEngineAuditable
函数snake_case,动词开头find_islandssolve_powerflow
方法snake_case,动词开头bus.add_load(...)
常量SCREAMING_SNAKE_CASEMAX_HISTORYDEFAULT_PORT
静态变量SCREAMING_SNAKE_CASEGLOBAL_CONFIG
局部变量snake_casebus_counttotal_load
泛型参数单大写字母或 UpperCamelCaseTAgentKind
生命周期短小写字母'a'ctx

错误处理

  • 使用 Result<T, EnerOSError>,禁止 unwrap 进入生产代码(测试除外)
  • 错误类型实现 thiserror::Error,提供 #[error("...")] 文本
  • 上层错误用 anyhow::Result 或自定义 Error 枚举聚合
  • ? 操作符附加上下文:map_err(|e| EnerOSError::network("connect", e))
// crates/eneros-powerflow/src/error.rs
use thiserror::Error;

#[derive(Debug, Error)]
pub enum PowerflowError {
    #[error("潮流计算不收敛,迭代次数 {iterations} 超过上限 {max_iterations}")]
    NonConvergence { iterations: usize, max_iterations: usize },

    #[error("母线 {bus_id} 电压越限:{voltage} p.u.,允许范围 [{v_min}, {v_max}]")]
    VoltageViolation {
        bus_id: u64,
        voltage: f64,
        v_min: f64,
        v_max: f64,
    },

    #[error("拓扑数据无效:{0}")]
    InvalidTopology(String),

    #[error("线性代数计算失败:{0}")]
    Linalg(#[from] eneros_linalg::LinalgError),
}

pub type Result<T> = std::result::Result<T, PowerflowError>;

异步规范

  • 统一使用 tokio runtime,避免阻塞调用混入异步上下文
  • 长时间 CPU 计算使用 tokio::task::spawn_blocking
  • 异步函数返回 impl Future<Output = Result<T>>Pin<Box<dyn Future...>>
  • 禁止在 async 块中调用 std::sync::Mutexlock().unwrap()
// 正确:异步 + spawn_blocking 处理 CPU 密集任务
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)
}

// 错误:阻塞调用混入异步上下文
pub async fn bad_solve(&self) -> Result<PowerflowResult> {
    std::thread::sleep(Duration::from_secs(1));  // 禁止!
    self.solve_blocking()
}

公共 API 文档

  • 公共 API 必须有文档注释 ///
  • 通过 cargo doc 校验,文档示例 /// ```rust 必须可编译
  • 复杂 API 提供 # Examples 章节
/// 求解给定拓扑的潮流方程。
///
/// 使用牛顿-拉夫逊法迭代求解,最大迭代次数由 `max_iterations` 控制。
/// 当相邻两次迭代电压差小于 `tolerance` 时认为收敛。
///
/// # 参数
/// - `topology`:电网拓扑,须已完成孤岛检测
/// - `max_iterations`:最大迭代次数,建议 20-50
/// - `tolerance`:收敛容差,默认 1e-8
///
/// # 返回
/// 返回 [`PowerflowResult`],包含每个母线的电压、相角与支路功率。
///
/// # 错误
/// - [`PowerflowError::NonConvergence`]:迭代次数超限仍未收敛
/// - [`PowerflowError::InvalidTopology`]:拓扑数据不完整或存在环
///
/// # 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> {
    // 实现
    unimplemented!()
}

提交信息规范

Conventional Commits

EnerOS 采用 Conventional Commits 1.0.0 规范:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]
字段说明
type提交类型,见下表
scope影响范围,通常为 crate 名(不含 eneros- 前缀),如 powerflowtopology
description简短描述,祈使句,首字母小写,结尾不加句号
body详细说明,每行 ≤ 72 字符
footer关联 Issue(Closes #123)、破坏性变更标记(BREAKING CHANGE:

提交类型

类型用途示例
feat新功能feat(powerflow): 支持 PV 母线无功上下限
fixBug 修复fix(topology): 修复孤岛检测漏判
docs文档变更docs(tutorials): 新增潮流计算教程
style代码风格(不影响逻辑)style(core): 统一使用 4 空格缩进
refactor重构(非新功能、非 Bug 修复)refactor(topology): 抽取图遍历公共方法
perf性能优化perf(timeseries): 优化写入路径减少内存拷贝
test测试相关test(constraint): 补充电压越限边界用例
build构建系统或依赖build(deny): 升级 cargo-deny 至 0.14
ciCI 配置ci(workflows): 新增 aarch64 构建矩阵
chore杂项(不影响源码或测试)chore: 更新 .gitignore
revert回滚之前的提交revert: 回滚 feat(powerflow): PV 无功限制

完整示例

feat(powerflow): 支持 PV 母线无功上下限

- 在 PvBus 中新增 q_min/q_max 字段,默认 -inf/+inf 保持向后兼容
- NewtonRaphsonSolver 在无功越限时自动将 PV 节点转为 PQ 节点
- 新增 bus_type_convertor 模块处理节点类型转换逻辑
- 补充 5 个单元测试覆盖越限场景

Closes #123
fix(topology): 修复孤岛检测在多源节点的漏判问题

孤立子图搜索时未考虑多源节点的合并,导致部分工业配网场景下
将同一个电气孤岛误判为两个。

修复方案:在 BFS 遍历前对源节点进行合并去重。

Closes #456

验证提交信息

# 安装 commitlint 工具
cargo install commitlint

# 在 commit-msg 钩子中校验
echo '#!/bin/sh
cargo commitlint --commit-msg-file "$1"' > .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg

分支策略与 Git 工作流

分支模型

EnerOS 采用简化的 Git Flow 模型:

分支用途命名生命周期
main主干,始终可发布main永久
release/*发布分支,仅用于 bug 修复release/0.47.0发布后归档
feat/*功能开发feat/powerflow-pv-limits合并后删除
fix/*Bug 修复fix/123-island-detection合并后删除
docs/*文档docs/tutorial-load-flow合并后删除
hotfix/*紧急修复(针对已发布版本)hotfix/0.47.1-cve-2024-xxxx合并后删除

工作流

main ─────●───●───●───●───●────────────────●─── (合并 PR)
           \                       \         /
            └─ feat/foo ──●───●─────●─── PR ──/
                          \    \    \
                           提交/测试/Review

1. 同步主干

# 始终基于最新 main 切分支
git checkout main
git pull upstream main
git checkout -b feat/my-feature

2. 开发与提交

# 小步提交,每个 commit 独立可编译
git add crates/eneros-powerflow/src/solver.rs
git commit -m "feat(powerflow): 实现 PV 转 PQ 节点逻辑"

git add crates/eneros-powerflow/src/bus_type.rs
git commit -m "feat(powerflow): 抽取节点类型转换器"

git add crates/eneros-powerflow/tests/pv_limits_test.rs
git commit -m "test(powerflow): 补充 PV 无功越限测试"

3. 保持线性历史

# 定期 rebase 至最新 main
git fetch upstream
git rebase upstream/main

# 解决冲突后继续
git add .
git rebase --continue

# 强制推送至个人 fork(使用 --force-with-lease 更安全)
git push origin feat/my-feature --force-with-lease

4. 处理 Review 反馈

# Reviewer 提出修改建议,修改后:
git add .
git commit --fixup <commit-sha>   # 创建 fixup 提交
git rebase -i --autosquash upstream/main

# 或直接修改最后一个 commit
git add .
git commit --amend --no-edit
git push origin feat/my-feature --force-with-lease

处理大型 PR

单一 PR 控制在 800 行以内,复杂改动拆分为多个 PR:

# 拆分策略:先合并基础设施 PR,再合并功能 PR
# PR #1: refactor(powerflow): 抽取节点类型转换器
# PR #2: feat(powerflow): 支持 PV 母线无功上下限(依赖 #1)
# PR #3: docs(powerflow): 补充 PV 节点教程(依赖 #2)

在 PR 描述中标注依赖关系:Depends on: #123

常见问题

链接错误

参考 安装与构建 - 常见构建问题。常见错误:

错误信息原因解决方案
linker 'cc' not found缺少 C 编译器Ubuntu: sudo apt install build-essential
failed to run custom build command for 'openssl-sys'OpenSSL 未找到设置 OPENSSL_DIR 或安装 libssl-dev
could not find library 'sqlite3'SQLite 开发库缺失Ubuntu: sudo apt install libsqlite3-dev
linking with 'link.exe' failed (Windows)MSVC 缺失安装 Visual Studio Build Tools

protobuf 版本不符

# 检查版本,须 ≥ 25
protoc --version
# libprotoc 27.0

# 升级
sudo apt install -y protobuf-compiler    # Ubuntu
brew upgrade protobuf                      # macOS
choco upgrade protobuf                     # Windows

大型 PR 拆分

单一 PR 控制在 800 行以内,复杂改动拆为多个:

  1. 基础设施 PR:抽取公共 trait、类型与工具函数
  2. 核心实现 PR:实现主要逻辑,依赖基础设施 PR
  3. 测试 PR:补充完整测试用例
  4. 文档 PR:更新 API 参考、教程与 CHANGELOG

每个 PR 在描述中标注依赖关系,按顺序合并。

cargo build 时内存不足

# 限制并行度
export CARGO_BUILD_JOBS=2
cargo build

# 或写入配置
# ~/.cargo/config.toml
[build]
jobs = 2

开发检查清单

每次提交 PR 前,请逐项检查:

  • cargo fmt --all -- --check 通过
  • cargo clippy --all-targets -- -D warnings 通过
  • cargo nextest run 全部通过
  • cargo nextest run -p <my-crate> 全部通过
  • 公共 API 变更已更新 cargo doc
  • 提交信息符合 Conventional Commits
  • 不包含密钥、证书、临时文件等敏感内容
  • 不包含 .env*.pem*.key 等敏感文件
  • cargo deny check 通过(无禁用许可或重复依赖)
  • 已更新 CHANGELOG.md(如涉及行为变更)

相关文档