Sikuwa first commit
Some checks are pending
CI / Test (Python 3.10 on macos-latest) (push) Waiting to run
CI / Test (Python 3.11 on macos-latest) (push) Waiting to run
CI / Test (Python 3.12 on macos-latest) (push) Waiting to run
CI / Test (Python 3.8 on macos-latest) (push) Waiting to run
CI / Test (Python 3.9 on macos-latest) (push) Waiting to run
CI / Test (Python 3.10 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.11 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.12 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.8 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.9 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.10 on windows-latest) (push) Waiting to run
CI / Test (Python 3.11 on windows-latest) (push) Waiting to run
CI / Test (Python 3.12 on windows-latest) (push) Waiting to run
CI / Test (Python 3.8 on windows-latest) (push) Waiting to run
CI / Test (Python 3.9 on windows-latest) (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Release (push) Blocked by required conditions
Documentation / Build Documentation (push) Waiting to run

This commit is contained in:
so陈
2026-02-20 23:53:48 +08:00
commit 13a1072c6f
57 changed files with 13519 additions and 0 deletions

44
.gitee/pipelines/ci.yml Normal file
View File

@@ -0,0 +1,44 @@
# Gitee Go 流水线配置
# 文档: https://gitee.com/help/articles/4295
name: gitee-go-pipeline
displayName: 'Sikuwa CI Pipeline'
triggers:
push:
- matchType: BRANCH
branch: master
- matchType: BRANCH
branch: develop
pull_request:
- matchType: BRANCH
branch: master
stages:
- name: test
displayName: '测试阶段'
strategy: naturally
trigger: auto
executor:
type: node-pool
labels:
- linux
steps:
- step: execute@python
name: setup_python
displayName: '设置 Python 环境'
pythonVersion: '3.10'
- step: execute@shell
name: install_deps
displayName: '安装依赖'
script: |
python -m pip install --upgrade pip
pip install click tomli tomli-w nuitka pytest
- step: execute@shell
name: run_tests
displayName: '运行测试'
script: |
pytest tests/ -v

32
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,32 @@
# CODEOWNERS
# 代码所有者配置
# 文档: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# 默认所有者
* @FORGE24
# 核心模块
/sikuwa/builder.py @FORGE24
/sikuwa/compiler.py @FORGE24
/sikuwa/config.py @FORGE24
/sikuwa/cli.py @FORGE24
# 增量编译模块
/sikuwa/incremental/ @FORGE24
# C++ 扩展
/sikuwa/cpp_cache/ @FORGE24
# 国际化
/sikuwa/i18n/ @FORGE24
# 文档
/docs/ @FORGE24
*.md @FORGE24
# CI/CD 配置
/.github/ @FORGE24
/.gitee/ @FORGE24
# 测试
/tests/ @FORGE24

61
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,61 @@
---
name: Bug 报告
about: 报告问题以帮助改进项目
title: '[BUG] '
labels: bug
assignees: ''
---
## 环境信息
| 项目 | 值 |
|:---|:---|
| 操作系统 | |
| Python 版本 | |
| Sikuwa 版本 | |
| Nuitka 版本 | |
## 问题描述
### 预期行为
清晰描述预期的行为。
### 实际行为
清晰描述实际发生的行为。
## 复现步骤
1. 执行命令 '...'
2. 配置 '...'
3. 查看 '...'
4. 出现错误
## 配置文件
如适用,请提供 `sikuwa.toml` 配置文件内容:
```toml
# 粘贴配置文件内容
```
## 日志信息
```
# 粘贴相关日志或错误信息
```
## 截图
如适用,添加截图以帮助说明问题。
## 其他信息
任何其他相关的上下文信息。
## 检查清单
- [ ] 已搜索现有 Issue确认问题未被报告
- [ ] 已使用最新版本测试
- [ ] 已提供完整的复现步骤

View File

@@ -0,0 +1,51 @@
---
name: 功能请求
about: 提出新功能或改进建议
title: '[FEATURE] '
labels: enhancement
assignees: ''
---
## 功能描述
清晰简洁地描述所需的功能。
## 使用场景
描述此功能将解决什么问题或满足什么需求。
## 期望方案
描述期望的解决方案或实现方式。
## 备选方案
描述考虑过的其他替代方案。
## 实现建议
如有技术实现建议,请在此描述:
```python
# 示例代码或伪代码
```
## 相关资源
- 参考链接
- 相关文档
- 类似项目实现
## 优先级评估
| 指标 | 评估 |
|:---|:---|
| 影响范围 | 低 / 中 / 高 |
| 实现难度 | 低 / 中 / 高 |
| 紧迫程度 | 低 / 中 / 高 |
## 检查清单
- [ ] 已搜索现有 Issue确认功能未被请求
- [ ] 已考虑功能的向后兼容性
- [ ] 愿意参与此功能的开发(如适用)

89
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,89 @@
## 变更描述
简要描述本次 Pull Request 的变更内容。
## 变更类型
请勾选适用的选项:
- [ ] Bug 修复 (fix)
- [ ] 新功能 (feat)
- [ ] 文档更新 (docs)
- [ ] 代码重构 (refactor)
- [ ] 性能优化 (perf)
- [ ] 测试相关 (test)
- [ ] 构建/CI (build/ci)
- [ ] 其他 (chore)
## 关联 Issue
Closes #(issue number)
## 变更详情
### 主要变更
- 变更点 1
- 变更点 2
- 变更点 3
### 技术说明
如需要,提供技术实现的详细说明。
## 测试说明
描述如何测试这些变更:
1. 测试步骤 1
2. 测试步骤 2
3. 预期结果
### 测试覆盖
- [ ] 添加了单元测试
- [ ] 添加了集成测试
- [ ] 手动测试通过
## 截图/录屏
如适用,添加截图或录屏展示变更效果。
## 检查清单
### 代码质量
- [ ] 代码符合项目风格规范
- [ ] 通过 `black` 格式化检查
- [ ] 通过 `mypy` 类型检查
- [ ] 无 linting 错误
### 测试
- [ ] 所有现有测试通过
- [ ] 添加了必要的新测试
- [ ] 测试覆盖率未降低
### 文档
- [ ] 更新了相关代码注释
- [ ] 更新了 README如需要
- [ ] 更新了 CHANGELOG如需要
- [ ] 更新了 API 文档(如需要)
### 兼容性
- [ ] 向后兼容
- [ ] 已考虑跨平台兼容性
- [ ] 无破坏性变更(或已在 CHANGELOG 中说明)
## 其他说明
任何其他需要审查者注意的信息。
## 审查要点
请审查者重点关注:
1. 关注点 1
2. 关注点 2

75
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,75 @@
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
name: Test (Python ${{ matrix.python-version }} on ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install click tomli tomli-w nuitka pytest pytest-cov
- name: Run tests
run: |
pytest tests/ -v
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black isort
- name: Check formatting
run: |
black --check . --exclude='.venv|build|dist' || true
isort --check . --skip=.venv --skip=build --skip=dist || true
release:
name: Release
runs-on: ubuntu-latest
needs: test
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

39
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Documentation
on:
push:
branches: [ main ]
paths:
- 'docs/**'
- 'sikuwa/**'
- 'README.md'
jobs:
build-docs:
name: Build Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[docs]"
- name: Build documentation
run: |
cd docs
make html
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/_build/html

59
.gitignore vendored Normal file
View File

@@ -0,0 +1,59 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
*.pyd
# Distribution / Build
dist/
build/
*.egg-info/
*.egg
*.whl
# Virtual Environment
.venv/
venv/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
# Sikuwa Build Outputs
sikuwa_logs/
sikuwa_build/
*.exe
*.dll
# C/C++ Build
*.o
*.obj
*.lib
*.a
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
# Cache
.smart_cache/
*.cache
.mypy_cache/
.pytest_cache/
# OS
.DS_Store
Thumbs.db
desktop.ini
# Logs
*.log
# Temporary
*.tmp
*.bak
*~

184
CHANGELOG.md Normal file
View File

@@ -0,0 +1,184 @@
# 更新日志
本文档记录 Sikuwa 项目的所有重要变更。
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
---
## [未发布]
### 计划中
- WebAssembly 编译目标支持
- 远程分布式编译
- 编译配置可视化工具
---
## [1.3.0] - 2026-01-31
### 新增
- **Native 编译模式**: 支持 Python 到 C/C++ 的转换,通过 GCC/G++ 编译为原生二进制
- 生成通用动态链接库 (.dll/.so)
- 不依赖 Python 专用格式 (.pyd)
- 支持静态链接选项
- 可保留生成的 C/C++ 源码用于审计
- **增量编译系统**: 实现"指哪编哪"的精确编译
- 函数级粒度的变更检测
- 依赖关系图追踪
- 编译缓存持久化
- 并行编译支持
- **C++ 智能缓存扩展**: 高性能缓存实现
- 基于 pybind11 的 Python 绑定
- LRU 缓存策略
- 内存映射文件支持
- **国际化支持 (i18n)**
- 内置中英文支持
- 基于 Babel 的翻译框架
- 可扩展的语言包机制
- **新增 CLI 命令**
- `sikuwa doctor`: 环境诊断
- `sikuwa validate`: 配置验证
### 变更
- 重构日志系统,支持多级别日志输出
- 优化构建流程,减少不必要的文件操作
- 改进配置文件解析,支持更复杂的嵌套结构
### 修复
- 修复 Windows 平台路径处理问题
- 修复大型项目编译时的内存溢出问题
- 修复并行编译时的竞态条件
- 修复 TOML 配置中特殊字符解析错误
### 性能
- 增量编译场景下构建速度提升 60%
- 缓存命中时跳过重复计算
- 优化依赖分析算法复杂度
---
## [1.2.0] - 2025-10-15
### 新增
- 初始公开发布
- 基于 Nuitka 的构建系统
- TOML 配置文件支持
- 跨平台构建 (Windows/Linux/macOS)
- Standalone 和 OneFile 模式
- 资源文件打包
### CLI 命令
- `sikuwa build`: 构建项目
- `sikuwa clean`: 清理构建文件
- `sikuwa init`: 初始化配置
- `sikuwa info`: 显示项目信息
- `sikuwa version`: 显示版本
### 配置选项
- 项目基本信息配置
- Nuitka 编译选项
- 平台特定配置
- 数据文件包含
---
## [1.1.0] - 2025-08-20
### 新增
- 插件系统支持
- 自定义构建钩子
### 变更
- 重构配置管理模块
### 修复
- 修复依赖检测遗漏问题
---
## [1.0.0] - 2025-06-01
### 新增
- 项目初始版本
- 基础构建功能
- 命令行界面原型
---
## 版本对比
| 版本 | 发布日期 | 主要特性 |
|:---|:---|:---|
| 1.3.0 | 2026-01-31 | Native 模式、增量编译、i18n |
| 1.2.0 | 2025-10-15 | 公开发布、完整 CLI |
| 1.1.0 | 2025-08-20 | 插件系统 |
| 1.0.0 | 2025-06-01 | 初始版本 |
---
## 迁移指南
### 从 1.2.x 升级到 1.3.x
**配置文件变更**
新增 `[sikuwa.native]` 配置节,用于 Native 编译模式:
```toml
# 新增配置
[sikuwa.native]
cc = "gcc"
cxx = "g++"
output_dll = true
output_exe = true
```
**CLI 变更**
`build` 命令新增参数:
```bash
# 新增 -m/--mode 参数
sikuwa build -m native
# 新增 --keep-c-source 参数
sikuwa build -m native --keep-c-source
```
**API 变更**
- `BuildConfig` 类新增 `compiler_mode` 属性
- `BuildConfig` 类新增 `native_options` 属性
- 新增 `NativeCompilerOptions` 数据类
---
## 链接
- [GitHub Releases](https://github.com/FORGE24/Sikuwa/releases)
- [Gitee Releases](https://gitee.com/FORGE24/Sikuwa/releases)
- [PyPI](https://pypi.org/project/sikuwa/)
[未发布]: https://github.com/FORGE24/Sikuwa/compare/v1.3.0...HEAD
[1.3.0]: https://github.com/FORGE24/Sikuwa/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/FORGE24/Sikuwa/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/FORGE24/Sikuwa/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/FORGE24/Sikuwa/releases/tag/v1.0.0

450
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,450 @@
# 贡献指南
感谢您对 Sikuwa 项目的关注。本文档将指导您如何参与项目贡献。
---
## 目录
- [行为准则](#行为准则)
- [如何贡献](#如何贡献)
- [开发环境](#开发环境)
- [代码规范](#代码规范)
- [提交规范](#提交规范)
- [Pull Request 流程](#pull-request-流程)
- [问题反馈](#问题反馈)
---
## 行为准则
参与本项目时,请遵守以下原则:
- 尊重所有贡献者
- 保持专业、友善的交流方式
- 接受建设性的批评和建议
- 专注于项目的最佳利益
---
## 如何贡献
### 贡献类型
| 类型 | 说明 |
|:---|:---|
| 报告 Bug | 通过 Issue 报告发现的问题 |
| 功能建议 | 提出新功能或改进建议 |
| 代码贡献 | 提交代码修复或新功能 |
| 文档改进 | 完善项目文档 |
| 测试用例 | 补充单元测试或集成测试 |
### 贡献流程
```
1. Fork 仓库
2. 创建分支
3. 编写代码
4. 运行测试
5. 提交更改
6. 创建 PR
7. 代码审查
8. 合并代码
```
---
## 开发环境
### 环境要求
| 组件 | 版本 |
|:---|:---|
| Python | >= 3.7 |
| pip | >= 21.0 |
| Git | >= 2.0 |
### 环境搭建
**1. Fork 并克隆仓库**
```bash
# GitHub
git clone https://github.com/<your-username>/Sikuwa.git
# Gitee
git clone https://gitee.com/<your-username>/Sikuwa.git
cd Sikuwa
```
**2. 创建虚拟环境**
```bash
# Windows
python -m venv .venv
.venv\Scripts\activate
# Linux/macOS
python3 -m venv .venv
source .venv/bin/activate
```
**3. 安装依赖**
```bash
pip install click tomli tomli-w nuitka pytest
```
**4. 验证安装**
```bash
python -m sikuwa --version
pytest tests/ -v
```
### 开发依赖
```
click>=8.0
tomli>=2.0 (Python < 3.11)
tomli-w>=1.0
nuitka>=2.0
pytest>=7.0
```
---
## 代码规范
### Python 代码风格
遵循 PEP 8 规范,并满足以下要求:
**格式化**
```bash
# 使用 black 格式化代码
black sikuwa/
# 使用 isort 排序导入
isort sikuwa/
```
**类型注解**
所有公共函数和方法必须包含类型注解:
```python
def build_project(
config: BuildConfig,
platform: Optional[str] = None,
verbose: bool = False
) -> bool:
"""构建项目"""
...
```
**文档字符串**
使用 Google 风格的 docstring
```python
def compile_module(source: Path, output: Path) -> CompileResult:
"""编译单个模块。
Args:
source: 源文件路径
output: 输出文件路径
Returns:
CompileResult: 编译结果对象
Raises:
CompileError: 编译失败时抛出
"""
...
```
### 命名规范
| 类型 | 规范 | 示例 |
|:---|:---|:---|
| 模块 | 小写下划线 | `smart_cache.py` |
| 类 | 大驼峰 | `SikuwaBuilder` |
| 函数/方法 | 小写下划线 | `build_project` |
| 常量 | 大写下划线 | `MAX_RETRY_COUNT` |
| 私有成员 | 单下划线前缀 | `_internal_method` |
### 代码检查
提交前运行代码检查:
```bash
# 类型检查
mypy sikuwa/
# 代码风格检查
black --check sikuwa/
isort --check sikuwa/
# 运行测试
pytest tests/ -v --cov=sikuwa
```
---
## 提交规范
### 提交信息格式
```
<type>(<scope>): <subject>
<body>
<footer>
```
### 类型说明
| 类型 | 说明 |
|:---|:---|
| `feat` | 新功能 |
| `fix` | Bug 修复 |
| `docs` | 文档更新 |
| `style` | 代码格式(不影响功能) |
| `refactor` | 代码重构(不是新功能或修复) |
| `perf` | 性能优化 |
| `test` | 测试相关 |
| `build` | 构建系统或外部依赖 |
| `ci` | CI 配置 |
| `chore` | 其他更改 |
### 范围说明
| 范围 | 说明 |
|:---|:---|
| `cli` | 命令行接口 |
| `builder` | 构建器 |
| `compiler` | 编译器 |
| `config` | 配置管理 |
| `cache` | 缓存系统 |
| `i18n` | 国际化 |
| `incremental` | 增量编译 |
### 示例
```
feat(compiler): add native compilation mode
- Implement Python to C/C++ translation
- Add GCC/G++ compilation support
- Support DLL/SO output format
Closes #123
```
```
fix(config): resolve TOML parsing error for nested tables
Fix the issue where nested tables in sikuwa.toml
were not parsed correctly when containing special characters.
Fixes #456
```
### 提交检查清单
- [ ] 代码通过所有测试
- [ ] 代码符合风格规范
- [ ] 添加了必要的测试用例
- [ ] 更新了相关文档
- [ ] 提交信息符合规范
---
## Pull Request 流程
### 创建 PR
1. 确保代码基于最新的 `main` 分支
```bash
git fetch upstream
git rebase upstream/main
```
2. 创建功能分支
```bash
git checkout -b feature/your-feature-name
```
3. 提交更改并推送
```bash
git add .
git commit -m "feat(scope): your commit message"
git push origin feature/your-feature-name
```
4. 在 GitHub/Gitee 上创建 Pull Request
### PR 模板
```markdown
## 变更描述
简要描述本次变更的内容。
## 变更类型
- [ ] Bug 修复
- [ ] 新功能
- [ ] 文档更新
- [ ] 代码重构
- [ ] 性能优化
- [ ] 其他
## 测试
描述如何测试这些变更。
## 检查清单
- [ ] 代码符合项目风格规范
- [ ] 自测通过
- [ ] 添加了相应的测试用例
- [ ] 更新了相关文档
## 关联 Issue
Closes #(issue number)
```
### PR 审查标准
- 代码质量符合规范
- 测试覆盖充分
- 文档完整
- 无安全隐患
- 性能影响可接受
---
## 问题反馈
### 报告 Bug
创建 Issue 时请包含以下信息:
1. **环境信息**
- 操作系统及版本
- Python 版本
- Sikuwa 版本
2. **问题描述**
- 预期行为
- 实际行为
- 复现步骤
3. **日志信息**
- 错误信息
- 相关日志输出
### Bug 报告模板
```markdown
## 环境信息
- OS: Windows 11
- Python: 3.10.5
- Sikuwa: 1.3.0
## 问题描述
### 预期行为
描述预期的行为。
### 实际行为
描述实际发生的行为。
### 复现步骤
1. 执行命令 `sikuwa build`
2. ...
3. ...
## 日志信息
```
粘贴相关日志
```
## 其他信息
任何其他相关信息。
```
### 功能建议
提出功能建议时请说明:
- 功能描述
- 使用场景
- 预期效果
- 可能的实现方案
---
## 分支管理
| 分支 | 说明 |
|:---|:---|
| `main` | 稳定版本分支 |
| `develop` | 开发分支 |
| `feature/*` | 功能开发分支 |
| `fix/*` | Bug 修复分支 |
| `release/*` | 发布准备分支 |
---
## 版本发布
### 版本号规范
遵循语义化版本 (Semantic Versioning)
```
MAJOR.MINOR.PATCH
```
| 部分 | 说明 |
|:---|:---|
| MAJOR | 不兼容的 API 变更 |
| MINOR | 向后兼容的功能新增 |
| PATCH | 向后兼容的问题修复 |
### 发布流程
1. 更新版本号
2. 更新 CHANGELOG
3. 创建发布分支
4. 执行测试
5. 合并到 main
6. 创建 Tag
7. 发布到 PyPI
---
## 联系方式
- GitHub Issues: [提交问题](https://github.com/FORGE24/Sikuwa/issues)
- Gitee Issues: [提交问题](https://gitee.com/FORGE24/Sikuwa/issues)
---
感谢您的贡献。

675
LICENSE Normal file
View File

@@ -0,0 +1,675 @@
# GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
## Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom
to share and change all versions of a program--to make sure it remains
free software for all its users. We, the Free Software Foundation, use
the GNU General Public License for most of our software; it applies
also to any other work released this way by its authors. You can apply
it to your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you
have certain responsibilities if you distribute copies of the
software, or if you modify it: responsibilities to respect the freedom
of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the
manufacturer can do so. This is fundamentally incompatible with the
aim of protecting users' freedom to change the software. The
systematic pattern of such abuse occurs in the area of products for
individuals to use, which is precisely where it is most unacceptable.
Therefore, we have designed this version of the GPL to prohibit the
practice for those products. If such problems arise substantially in
other domains, we stand ready to extend this provision to those
domains in future versions of the GPL, as needed to protect the
freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish
to avoid the special danger that patents applied to a free program
could make it effectively proprietary. To prevent this, the GPL
assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
## TERMS AND CONDITIONS
### 0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.
An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
### 1. Source Code.
The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same
work.
### 2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.
### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the general
public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
### 7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.
### 8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
### 11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions
of the GNU General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU General Public
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that numbered version or
of any later version published by the Free Software Foundation. If the
Program does not specify a version number of the GNU General Public
License, you may choose any version ever published by the Free
Software Foundation.
If the Program specifies that a proxy can decide which future versions
of the GNU General Public License can be used, that proxy's public
statement of acceptance of a version permanently authorizes you to
choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper
mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands \`show w' and \`show c' should show the
appropriate parts of the General Public License. Of course, your
program's commands might be different; for a GUI interface, you would
use an "about box".
You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU GPL, see <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your
program into proprietary programs. If your program is a subroutine
library, you may consider it more useful to permit linking proprietary
applications with the library. If this is what you want to do, use the
GNU Lesser General Public License instead of this License. But first,
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.

56
LICENSE.CHINESE Normal file
View File

@@ -0,0 +1,56 @@
GNU 宽通用公共许可证
第三版2007 年 6 月 29 日
版权所有 (C) 2007 自由软件基金会 http://fsf.org/
任何人皆可复制和分发本许可证的完整副本,但不允许修改之。
本版本的 GNU 宽通用公共许可证包含了 GNU 通用公共许可证第三版的条款与条件,并补充了下列附加许可。
0. 附加定义
在此处,“本许可证”指 GNU 宽通用公共许可证第三版“GNU GPL” 指 GNU 通用公共许可证第三版。
“本库”指受本许可证管理的涵盖作品,有别于后文定义的应用程序或组合作品。
“应用程序”指任何使用了本库所提供的接口的作品,但此类作品并非基于本库。为本库所定义的类定义一个子类,视为对本库所定义的接口的一种使用形式。
“组合作品”指将应用程序和本库组合或链接在一起而产生的作品。参与形成了组合作品的本库特定版本又称“链接版”。
组合作品的“必要对应源代码”指该组合作品的对应源代码,其不包括在该组合作品的任何部分的源代码中,在单独判断时,是基于应用程序,而非基于链接版的源代码。
组合作品的“对应应用程序代码”指该应用程序的目标代码和/或源代码,其包括从该应用程序生成组合作品这一过程所需的一切数据和实用程序,但不包括该组合作品的系统库。
1. GNU GPL 第 3 节的例外
你可以根据本许可证的第 3 节和第 4 节来输送一份涵盖作品,而不受 GNU GPL 第 3 节约束。
2. 输送修改版
如果你对本库的一份副本进行了修改,且在你的修改中,某一设施引用了某个应用程序将会提供的函数或数据,且该应用程序使用了该设施(而非在该设施被调用时以参数传递),那么你可以输送该修改版的副本,输送方式如下:
a在本许可证下输送前提是你尽力确保在应用程序不提供函数或数据时该设施仍然能够运转并执行剩余目标的任何有意义的部分
b在 GNU GPL 下输送,且本许可证的附加许可对该副本不适用。
3. 目标代码包含来自库的头文件的材料
应用程序的目标代码形式可以包含来自本库的头文件的材料。你可以在你选定的条款下输送这样的目标代码,前提是,如果你包含的材料不限于数字参数、数据结构设计和存取器,或小型宏、内联函数和模板(长度不多于 10 行),则你要做到以下两点:
a为目标代码的每份副本附上显著的声明说明其中用到了本库且本库及对本库的使用行为受本许可证约束。
b随目标代码附带一份 GNU GPL 及本许可证的副本。
4. 组合作品
你可以在你选定的条款下输送一份组合作品,组合作品的条款在效果上不得限制对组合作品中包含的本库部分的修改,也不得限制为调试上述修改而进行的逆向工程,并且你还要做到以下各点:
a为组合作品的每份副本附上显著的声明说明其中用到了本库且本库及对本库的使用行为受本许可证约束。
b随组合作品附带一份 GNU GPL 及本许可证的副本。
c对于在运行期间会显示版权声明的组合作品请在该版权声明中包含本库的版权声明并为用户指出 GNU GPL 及本许可证的副本位置。
d做到以下之一
0在本许可证下输送必要对应源代码并以如下方式输送对应应用程序代码在形式上适合、且在条款上允许用户使用链接版的修改版来重新组合或重新链接该应用程序以产生一份组合作品的修改版且输送对应源代码的方式满足 GNU GPL 第 6 节所指定的方式。
1采用一种合适的共享库的机制以便于与本库的链接。合适的机制满足a在运行时使用已经存在于用户的计算机系统中的本库的副本b在使用与链接版的接口相兼容的本库的修改版时仍能正常运行。
e提供安装信息但仅当你受 GNU GPL 第 6 节所要求时才需提供此信息,并且只需提供必要的信息,以供安装和运行一份由应用程序与该链接版的修改版相重新组合或重新链接,而产生的组合作品的修改版。(如果你选择 4d0 项,则安装信息必须伴以必要对应源代码和对应应用程序代码。如果你选择 4d1 项,则你必须根据 GNU GPL 第 6 节指定的输送对应源代码的方式来提供安装信息。)
5. 组合库
对于基于本库的作品的库设施,你可以将它们与其他非应用程序且非受本许可证约束的库设施,一起放在单个库中,并且你可以在你选定的条款下输送该组合库,前提是你做到以下两点:
a为组合库附带一份基于本库的同一作品的副本而不附带任何其他的库设施并在本许可证的条款下输送。
b为组合库附上显著的声明说明组合库的一部分是基于本库的作品并说明何处可以找到附带的同一作品的未组合形式。
6. GNU 宽通用公共许可证的修订版
自由软件基金会可能会不定时发布 GNU 宽通用公共许可证的修订版和/或新版。新版将秉承当前版本的精神,但在细节上会有差异,以应对新的问题或事项。
每一版都会有不同的版本号。如果你接收到的本库指定其使用 GNU 宽通用公共许可证的特定版本“或任何后续的版本”,你可以选择遵守该版本或者自由软件基金会发布的任何后续版本的条款与条件。如果你接收到的本库没有指定 GNU 宽通用公共许可证的版本,你可以选用自由软件基金会发布的任意版本的 GNU 宽通用公共许可证。
如果你接收的本库指定一位代理人来决定使用哪个将来的 GNU 宽通用公共许可证的版本,则该代理人通过公开声明采用的版本,永久许可你为本库使用该版本。

496
README.md Normal file
View File

@@ -0,0 +1,496 @@
# Sikuwa
<p align="center">
<strong>基于 Nuitka 的跨平台 Python 项目打包工具</strong>
</p>
<p align="center">
<a href="https://github.com/FORGE24/Sikuwa/releases"><img src="https://img.shields.io/github/v/release/FORGE24/Sikuwa?style=flat-square&logo=github" alt="GitHub Release"></a>
<a href="https://github.com/FORGE24/Sikuwa/blob/main/LICENSE"><img src="https://img.shields.io/github/license/FORGE24/Sikuwa?style=flat-square" alt="License"></a>
<a href="https://github.com/FORGE24/Sikuwa/stargazers"><img src="https://img.shields.io/github/stars/FORGE24/Sikuwa?style=flat-square&logo=github" alt="GitHub Stars"></a>
<a href="https://github.com/FORGE24/Sikuwa/issues"><img src="https://img.shields.io/github/issues/FORGE24/Sikuwa?style=flat-square" alt="Issues"></a>
<img src="https://img.shields.io/badge/python-3.7%2B-blue?style=flat-square&logo=python" alt="Python Version">
</p>
<p align="center">
<a href="#features">功能特性</a> |
<a href="#installation">安装</a> |
<a href="#quick-start">快速开始</a> |
<a href="#documentation">文档</a> |
<a href="#contributing">贡献指南</a>
</p>
---
## 概述
Sikuwa 是一款专业的 Python 项目打包工具,基于 Nuitka 编译器构建,支持将 Python 项目编译为独立的可执行文件。提供两种编译模式,满足不同场景的需求。
### 核心特性
| 特性 | 描述 |
|------|------|
| 双模式编译 | 支持 Nuitka 模式和 Native 原生编译模式 |
| 跨平台支持 | Windows、Linux、macOS 全平台构建 |
| 增量编译 | 智能检测变更,仅编译修改的部分 |
| 配置驱动 | 基于 TOML 的声明式配置 |
| 国际化 | 内置多语言支持 (i18n) |
| 高性能缓存 | C++ 实现的智能缓存系统 |
---
## Features
### 编译模式
#### Nuitka 模式 (默认)
使用 Nuitka 编译器将 Python 代码编译为优化的机器码。
- 完整的 Python 兼容性
- 自动依赖分析与打包
- 支持 Standalone 和 OneFile 模式
- 内置插件系统
#### Native 模式
将 Python 代码转换为 C/C++ 源码,通过 GCC/G++ 编译为原生二进制文件。
- 生成通用动态链接库 (.dll/.so)
- 不依赖 Python 专用格式 (.pyd)
- 支持静态链接
- 可保留生成的 C/C++ 源码用于审计
### 增量编译系统
基于依赖图的智能增量编译,实现"指哪编哪"的精确编译策略。
```
源码改变 -> 依赖分析 -> 影响范围计算 -> 最小化重编译
```
- 函数级粒度的变更检测
- 依赖关系追踪
- 编译缓存持久化
- 并行编译支持
---
## Installation
### 系统要求
| 组件 | 版本要求 |
|------|----------|
| Python | >= 3.7 |
| Nuitka | >= 2.0 |
| GCC/G++ | >= 8.0 (Native 模式) |
| CMake | >= 3.16 (可选,用于 C++ 扩展) |
### 获取工具链
```bash
git clone https://github.com/FORGE24/Sikuwa.git
cd Sikuwa
```
### 安装依赖
```bash
pip install click tomli tomli-w nuitka
```
### 运行方式
```bash
# 直接运行
python -m sikuwa --version
python -m sikuwa doctor
# 或添加别名 (PowerShell)
Set-Alias sikuwa "python -m sikuwa"
# 或添加别名 (Bash)
alias sikuwa='python -m sikuwa'
```
---
## Quick Start
### 1. 初始化项目
```bash
sikuwa init
```
该命令将在当前目录创建 `sikuwa.toml` 配置文件。
### 2. 配置项目
编辑 `sikuwa.toml`
```toml
[sikuwa]
project_name = "myproject"
version = "1.0.0"
main_script = "src/main.py"
src_dir = "src"
output_dir = "dist"
platforms = ["windows", "linux"]
[sikuwa.nuitka]
standalone = true
onefile = false
include_packages = [
"requests",
"numpy",
]
```
### 3. 构建项目
```bash
# 使用 Nuitka 模式构建
sikuwa build
# 使用 Native 模式构建
sikuwa build -m native
# 构建指定平台
sikuwa build -p windows
# 详细输出模式
sikuwa build -v
```
### 4. 清理构建文件
```bash
sikuwa clean
```
---
## 命令行参考
### 命令列表
| 命令 | 描述 |
|------|------|
| `build` | 构建项目 |
| `clean` | 清理构建文件 |
| `init` | 初始化项目配置 |
| `info` | 显示项目信息 |
| `validate` | 验证配置文件 |
| `doctor` | 检查构建环境 |
| `version` | 显示版本信息 |
### build 命令选项
| 选项 | 简写 | 描述 |
|------|------|------|
| `--config` | `-c` | 指定配置文件路径 |
| `--platform` | `-p` | 目标平台 (windows/linux/macos) |
| `--mode` | `-m` | 编译模式 (nuitka/native) |
| `--verbose` | `-v` | 详细输出模式 |
| `--force` | `-f` | 强制重新构建 |
| `--keep-c-source` | - | 保留生成的 C/C++ 源码 |
---
## 配置文件详解
### 基础配置
```toml
[sikuwa]
# 项目基本信息
project_name = "myproject" # 项目名称
version = "1.0.0" # 版本号
description = "项目描述" # 可选
author = "作者名" # 可选
# 构建路径配置
main_script = "main.py" # 入口脚本
src_dir = "." # 源码目录
output_dir = "dist" # 输出目录
build_dir = "build" # 构建临时目录
# 目标平台
platforms = ["windows", "linux", "macos"]
```
### Nuitka 选项
```toml
[sikuwa.nuitka]
# 基础选项
standalone = true # 独立模式
onefile = false # 单文件模式
follow_imports = true # 跟踪导入
show_progress = true # 显示进度
enable_console = true # 启用控制台
# 优化选项
optimize = true # 启用优化
lto = false # 链接时优化
# Windows 特定选项
windows_icon = "icon.ico"
windows_company_name = "Company"
windows_product_name = "Product"
# macOS 特定选项
macos_app_bundle = false
macos_icon = "icon.icns"
# 包含/排除
include_packages = ["package1", "package2"]
include_modules = ["module1"]
nofollow_imports = ["test_*"]
# 插件
enable_plugins = ["pyside6", "numpy"]
# 数据文件
include_data_dirs = [
{ src = "assets", dest = "assets" }
]
# 额外参数
extra_args = [
"--assume-yes-for-downloads"
]
```
### Native 编译选项
```toml
[sikuwa.native]
# 编译器选择
cc = "gcc" # C 编译器
cxx = "g++" # C++ 编译器
# 编译标志
c_flags = ["-O2", "-fPIC"]
cxx_flags = ["-O2", "-fPIC", "-std=c++17"]
link_flags = []
# 输出选项
output_dll = true # 生成动态库
output_exe = true # 生成可执行文件
output_static = false # 生成静态库
# Python 嵌入
embed_python = true # 嵌入 Python
python_static = false # 静态链接 Python
# 优化选项
lto = false # 链接时优化
strip = true # 剥离符号
# 调试选项
debug = false # 调试模式
keep_c_source = false # 保留 C/C++ 源码
```
---
## 项目结构
```
sikuwa/
├── __init__.py # 包初始化
├── __main__.py # 入口点
├── cli.py # 命令行接口
├── config.py # 配置管理
├── builder.py # 构建器核心
├── compiler.py # Native 编译器
├── parser.py # 代码解析器
├── log.py # 日志系统
├── i18n.py # 国际化支持
├── nuitka_loader.py # Nuitka 加载器
├── cpp_cache/ # C++ 缓存扩展
│ ├── smart_cache.cpp
│ ├── smart_cache.h
│ └── pysmartcache.cpp
├── incremental/ # 增量编译模块
│ ├── core.py # 核心实现
│ ├── analyzer.py # 代码分析器
│ ├── smart_cache.py # 智能缓存
│ └── compiler_integration.py
└── i18n/ # 国际化资源
└── locales/
└── en_US/
```
---
## Documentation
### 在线文档
- [官方文档](https://www.sanrol-cloud.top)
- [API 参考](https://www.sanrol-cloud.top/api)
- [示例项目](https://github.com/FORGE24/Sikuwa/tree/main/examples)
### 本地文档
```bash
# 生成文档
cd docs
make html
```
---
## Contributing
欢迎贡献代码、报告问题或提出改进建议。
### 贡献流程
1. Fork 本仓库
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 创建 Pull Request
### 开发环境设置
```bash
# 克隆仓库
git clone https://github.com/FORGE24/Sikuwa.git
cd Sikuwa
# 创建虚拟环境
python -m venv .venv
.venv\Scripts\activate # Windows
source .venv/bin/activate # Linux/macOS
# 安装依赖
pip install click tomli tomli-w nuitka pytest
# 运行测试
pytest tests/
```
### 代码规范
- 遵循 PEP 8 代码风格
- 使用类型注解
- 编写单元测试
- 更新相关文档
### 提交规范
提交信息格式:
```
<type>(<scope>): <subject>
<body>
<footer>
```
类型 (type)
- `feat`: 新功能
- `fix`: 修复 Bug
- `docs`: 文档更新
- `style`: 代码格式
- `refactor`: 重构
- `test`: 测试相关
- `chore`: 构建/工具
---
## 常见问题
<details>
<summary><b>Q: 构建时提示找不到 Nuitka</b></summary>
A: 运行 `sikuwa doctor` 检查环境,确保已安装 Nuitka
```bash
pip install nuitka
```
</details>
<details>
<summary><b>Q: Native 模式需要哪些编译器?</b></summary>
A: Native 模式需要 GCC/G++ 编译器。Windows 用户推荐安装 MinGW-w64 或使用 MSYS2。
</details>
<details>
<summary><b>Q: 如何减少构建产物体积?</b></summary>
A:
1. 使用 `onefile = true` 生成单文件
2. 启用 `lto = true` 链接时优化
3. 使用 `nofollow_imports` 排除不需要的模块
</details>
<details>
<summary><b>Q: 支持哪些 Python 版本?</b></summary>
A: 支持 Python 3.7 及以上版本。推荐使用 Python 3.10+。
</details>
---
## 更新日志
### v1.3.0 (2026-01-31)
**新特性**
- 新增 Native 编译模式
- 增量编译系统
- C++ 智能缓存扩展
- 国际化支持
**改进**
- 优化构建性能
- 改进日志系统
- 更完善的错误处理
**修复**
- 修复 Windows 路径处理问题
- 修复大型项目编译超时问题
### v1.2.0
- 初始公开发布
- 基础 Nuitka 构建支持
- 命令行界面
查看完整更新日志:[CHANGELOG.md](CHANGELOG.md)
---
## 许可证
本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
---
## 致谢
- [Nuitka](https://nuitka.net/) - Python 编译器
- [Click](https://click.palletsprojects.com/) - 命令行框架
- [pybind11](https://github.com/pybind/pybind11) - C++/Python 绑定
---
## 联系方式
- GitHub Issues: [提交问题](https://github.com/FORGE24/Sikuwa/issues)
- 官方网站: [https://www.sanrol-cloud.top](https://www.sanrol-cloud.top)
---
<p align="center">
<sub>Made with dedication by Sikuwa Team</sub>
</p>

462
README_GITEE.md Normal file
View File

@@ -0,0 +1,462 @@
# Sikuwa
<p align="center">
<strong>基于 Nuitka 的跨平台 Python 项目打包工具</strong>
</p>
<p align="center">
<a href="https://gitee.com/FORGE24/Sikuwa/releases"><img src="https://gitee.com/FORGE24/Sikuwa/badge/star.svg?theme=dark" alt="Gitee Stars"></a>
<a href="https://gitee.com/FORGE24/Sikuwa/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
<img src="https://img.shields.io/badge/python-3.7%2B-blue.svg" alt="Python Version">
<img src="https://img.shields.io/badge/platform-windows%20%7C%20linux%20%7C%20macos-lightgrey.svg" alt="Platform">
</p>
<p align="center">
<a href="#功能特性">功能特性</a> |
<a href="#安装说明">安装说明</a> |
<a href="#快速开始">快速开始</a> |
<a href="#文档">文档</a> |
<a href="#贡献指南">贡献指南</a>
</p>
---
## 项目简介
Sikuwa 是一款专业的 Python 项目打包工具,基于 Nuitka 编译器构建,支持将 Python 项目编译为独立的可执行文件。提供两种编译模式,满足不同场景的需求。
---
## 功能特性
### 核心功能
| 特性 | 说明 |
|:---:|:---|
| 双模式编译 | 支持 Nuitka 模式和 Native 原生编译模式 |
| 跨平台支持 | Windows、Linux、macOS 全平台构建 |
| 增量编译 | 智能检测变更,仅编译修改的部分 |
| 配置驱动 | 基于 TOML 的声明式配置 |
| 国际化 | 内置多语言支持 (i18n) |
| 高性能缓存 | C++ 实现的智能缓存系统 |
### 编译模式详解
**Nuitka 模式 (默认)**
使用 Nuitka 编译器将 Python 代码编译为优化的机器码。
- 完整的 Python 兼容性
- 自动依赖分析与打包
- 支持 Standalone 和 OneFile 模式
- 内置插件系统
**Native 模式**
将 Python 代码转换为 C/C++ 源码,通过 GCC/G++ 编译为原生二进制文件。
- 生成通用动态链接库 (.dll/.so)
- 不依赖 Python 专用格式 (.pyd)
- 支持静态链接
- 可保留生成的 C/C++ 源码用于审计
### 增量编译系统
基于依赖图的智能增量编译,实现"指哪编哪"的精确编译策略。
```
源码改变 -> 依赖分析 -> 影响范围计算 -> 最小化重编译
```
技术特点:
- 函数级粒度的变更检测
- 依赖关系追踪
- 编译缓存持久化
- 并行编译支持
---
## 安装说明
### 环境要求
| 组件 | 版本要求 |
|:---:|:---:|
| Python | >= 3.7 |
| Nuitka | >= 2.0 |
| GCC/G++ | >= 8.0 (Native 模式) |
| CMake | >= 3.16 (可选) |
### 获取工具链
```bash
git clone https://gitee.com/CHENshidi__2020/Sikuwa.git
cd Sikuwa
```
### 安装依赖
```bash
pip install click tomli tomli-w nuitka
```
### 运行方式
```bash
# 直接运行
python -m sikuwa --version
python -m sikuwa doctor
# 或添加别名 (PowerShell)
Set-Alias sikuwa "python -m sikuwa"
# 或添加别名 (Bash)
alias sikuwa='python -m sikuwa'
```
---
## 快速开始
### 初始化项目
```bash
sikuwa init
```
该命令将在当前目录创建 `sikuwa.toml` 配置文件。
### 配置项目
编辑 `sikuwa.toml`
```toml
[sikuwa]
project_name = "myproject"
version = "1.0.0"
main_script = "src/main.py"
src_dir = "src"
output_dir = "dist"
platforms = ["windows", "linux"]
[sikuwa.nuitka]
standalone = true
onefile = false
include_packages = [
"requests",
"numpy",
]
```
### 构建项目
```bash
# 使用 Nuitka 模式构建
sikuwa build
# 使用 Native 模式构建
sikuwa build -m native
# 构建指定平台
sikuwa build -p windows
# 详细输出模式
sikuwa build -v
```
### 清理构建
```bash
sikuwa clean
```
---
## 命令行参考
### 可用命令
| 命令 | 功能说明 |
|:---|:---|
| `sikuwa build` | 构建项目 |
| `sikuwa clean` | 清理构建文件 |
| `sikuwa init` | 初始化项目配置 |
| `sikuwa info` | 显示项目信息 |
| `sikuwa validate` | 验证配置文件 |
| `sikuwa doctor` | 检查构建环境 |
| `sikuwa version` | 显示版本信息 |
### build 命令参数
| 参数 | 简写 | 说明 |
|:---|:---:|:---|
| `--config` | `-c` | 指定配置文件路径 |
| `--platform` | `-p` | 目标平台 (windows/linux/macos) |
| `--mode` | `-m` | 编译模式 (nuitka/native) |
| `--verbose` | `-v` | 详细输出模式 |
| `--force` | `-f` | 强制重新构建 |
| `--keep-c-source` | - | 保留生成的 C/C++ 源码 |
---
## 配置文件
### 基础配置
```toml
[sikuwa]
# 项目基本信息
project_name = "myproject" # 项目名称
version = "1.0.0" # 版本号
description = "项目描述" # 可选
author = "作者名" # 可选
# 构建路径配置
main_script = "main.py" # 入口脚本
src_dir = "." # 源码目录
output_dir = "dist" # 输出目录
build_dir = "build" # 构建临时目录
# 目标平台
platforms = ["windows", "linux", "macos"]
```
### Nuitka 配置
```toml
[sikuwa.nuitka]
# 基础选项
standalone = true # 独立模式
onefile = false # 单文件模式
follow_imports = true # 跟踪导入
show_progress = true # 显示进度
enable_console = true # 启用控制台
# 优化选项
optimize = true # 启用优化
lto = false # 链接时优化
# Windows 选项
windows_icon = "icon.ico"
windows_company_name = "Company"
windows_product_name = "Product"
# macOS 选项
macos_app_bundle = false
macos_icon = "icon.icns"
# 包管理
include_packages = ["package1", "package2"]
include_modules = ["module1"]
nofollow_imports = ["test_*"]
# 插件
enable_plugins = ["pyside6", "numpy"]
# 数据文件
include_data_dirs = [
{ src = "assets", dest = "assets" }
]
# 额外参数
extra_args = [
"--assume-yes-for-downloads"
]
```
### Native 编译配置
```toml
[sikuwa.native]
# 编译器
cc = "gcc" # C 编译器
cxx = "g++" # C++ 编译器
# 编译标志
c_flags = ["-O2", "-fPIC"]
cxx_flags = ["-O2", "-fPIC", "-std=c++17"]
link_flags = []
# 输出选项
output_dll = true # 生成动态库
output_exe = true # 生成可执行文件
output_static = false # 生成静态库
# Python 嵌入
embed_python = true # 嵌入 Python
python_static = false # 静态链接 Python
# 优化选项
lto = false # 链接时优化
strip = true # 剥离符号
# 调试选项
debug = false # 调试模式
keep_c_source = false # 保留 C/C++ 源码
```
---
## 项目结构
```
sikuwa/
|-- __init__.py # 包初始化
|-- __main__.py # 入口点
|-- cli.py # 命令行接口
|-- config.py # 配置管理
|-- builder.py # 构建器核心
|-- compiler.py # Native 编译器
|-- parser.py # 代码解析器
|-- log.py # 日志系统
|-- i18n.py # 国际化支持
|-- nuitka_loader.py # Nuitka 加载器
|-- cpp_cache/ # C++ 缓存扩展
| |-- smart_cache.cpp
| |-- smart_cache.h
| +-- pysmartcache.cpp
|-- incremental/ # 增量编译模块
| |-- core.py # 核心实现
| |-- analyzer.py # 代码分析器
| |-- smart_cache.py # 智能缓存
| +-- compiler_integration.py
+-- i18n/ # 国际化资源
+-- locales/
+-- en_US/
```
---
## 文档
- 官方文档: [https://www.sanrol-cloud.top](https://www.sanrol-cloud.top)
- API 参考: [https://www.sanrol-cloud.top/api](https://www.sanrol-cloud.top/api)
---
## 贡献指南
欢迎贡献代码、报告问题或提出改进建议。
### 贡献流程
1. Fork 本仓库
2. 创建特性分支 (`git checkout -b feature/NewFeature`)
3. 提交更改 (`git commit -m 'Add NewFeature'`)
4. 推送到分支 (`git push origin feature/NewFeature`)
5. 创建 Pull Request
### 开发环境
```bash
# 克隆仓库
git clone https://gitee.com/CHENshidi__2020/Sikuwa.git
cd Sikuwa
# 创建虚拟环境
python -m venv .venv
.venv\Scripts\activate # Windows
source .venv/bin/activate # Linux/macOS
# 安装依赖
pip install click tomli tomli-w nuitka pytest
# 运行测试
pytest tests/
```
### 代码规范
- 遵循 PEP 8 代码风格
- 使用类型注解
- 编写单元测试
- 更新相关文档
### 提交规范
```
<类型>(<范围>): <简述>
<详细说明>
<关联信息>
```
类型说明:
- `feat`: 新功能
- `fix`: 修复 Bug
- `docs`: 文档更新
- `style`: 代码格式
- `refactor`: 重构
- `test`: 测试相关
- `chore`: 构建/工具
---
## 常见问题
**Q: 构建时提示找不到 Nuitka**
运行 `sikuwa doctor` 检查环境,确保已安装 Nuitka
```bash
pip install nuitka
```
**Q: Native 模式需要哪些编译器**
Native 模式需要 GCC/G++ 编译器。Windows 用户推荐安装 MinGW-w64 或使用 MSYS2。
**Q: 如何减少构建产物体积**
1. 使用 `onefile = true` 生成单文件
2. 启用 `lto = true` 链接时优化
3. 使用 `nofollow_imports` 排除不需要的模块
**Q: 支持哪些 Python 版本**
支持 Python 3.7 及以上版本。推荐使用 Python 3.10+。
---
## 更新日志
### v1.3.0 (2026-01-31)
**新特性**
- 新增 Native 编译模式
- 增量编译系统
- C++ 智能缓存扩展
- 国际化支持
**改进**
- 优化构建性能
- 改进日志系统
- 更完善的错误处理
**修复**
- 修复 Windows 路径处理问题
- 修复大型项目编译超时问题
---
## 许可证
本项目采用 MIT 许可证。详见 [LICENSE](LICENSE) 文件。
---
## 相关链接
- Gitee: [https://gitee.com/FORGE24/Sikuwa](https://gitee.com/FORGE24/Sikuwa)
- GitHub: [https://github.com/FORGE24/Sikuwa](https://github.com/FORGE24/Sikuwa)
- 官网: [https://www.sanrol-cloud.top](https://www.sanrol-cloud.top)
- 问题反馈: [https://gitee.com/FORGE24/Sikuwa/issues](https://gitee.com/FORGE24/Sikuwa/issues)
---
## 致谢
- [Nuitka](https://nuitka.net/) - Python 编译器
- [Click](https://click.palletsprojects.com/) - 命令行框架
- [pybind11](https://github.com/pybind/pybind11) - C++/Python 绑定

78
SECURITY.md Normal file
View File

@@ -0,0 +1,78 @@
# 安全策略
## 支持的版本
以下版本目前接受安全更新:
| 版本 | 支持状态 |
|:---|:---:|
| 1.3.x | 支持 |
| 1.2.x | 支持 |
| < 1.2 | 不支持 |
## 报告漏洞
如果您发现安全漏洞,请按照以下步骤报告:
### 请勿公开报告
请不要通过公开的 Issue 报告安全漏洞。
### 报告方式
1. 发送邮件至安全团队
2. 使用 GitHub/Gitee 的私密漏洞报告功能
### 报告内容
请在报告中包含以下信息:
- 漏洞类型
- 受影响的版本
- 复现步骤
- 潜在影响
- 建议的修复方案(如有)
### 响应时间
- 确认收到48 小时内
- 初步评估7 个工作日内
- 修复发布:根据严重程度,通常在 30 天内
### 漏洞披露
修复发布后,我们将:
1. 发布安全公告
2. 更新 CHANGELOG
3. 通知受影响用户(如适用)
## 安全更新
建议用户:
- 及时更新到最新版本
- 订阅安全公告
- 定期检查依赖项的安全更新
## 安全最佳实践
使用 Sikuwa 时的安全建议:
### 配置文件
- 不要在配置文件中存储敏感信息
- 使用环境变量管理密钥
- 将配置文件添加到 `.gitignore`
### 构建环境
- 使用虚拟环境隔离依赖
- 定期更新依赖项
- 验证第三方包的完整性
### 输出文件
- 审查生成的可执行文件
- 使用代码签名(如适用)
- 扫描构建产物的安全漏洞

8
__init__.py Normal file
View File

@@ -0,0 +1,8 @@
# sikuwa/__init__.py
"""
Sikuwa 构建工具
一个基于 Nuitka 的 Python 项目打包工具
"""
__version__ = "1.3.0"
__author__ = "Sikuwa Team"

24
__main__.py Normal file
View File

@@ -0,0 +1,24 @@
# sikuwa/__main__.py
"""
Sikuwa 入口点
"""
def main():
"""主入口函数 - 延迟导入避免循环依赖"""
try:
# 延迟导入,避免 mypyc 编译问题
from sikuwa.cli import main as cli_main
cli_main()
except ImportError as e:
print(f"[错误] 导入失败: {e}")
print("请确保已安装所有依赖: pip install -r requirements.txt")
import sys
sys.exit(1)
except Exception as e:
print(f"[错误] 运行失败: {e}")
import sys
sys.exit(1)
if __name__ == "__main__":
main()

8
babel.cfg Normal file
View File

@@ -0,0 +1,8 @@
# Babel configuration file
# For extracting and managing translation strings
[python: **/*.py]
encoding = utf-8
[extractors]
# Custom extractors can be added here

1192
builder.py Normal file

File diff suppressed because it is too large Load Diff

829
cli.py Normal file
View File

@@ -0,0 +1,829 @@
# sikuwa/cli.py
"""
Sikuwa 命令行界面
"""
import click
import sys
from pathlib import Path
from typing import Optional
from sikuwa.config import ConfigManager, BuildConfig, create_config
from sikuwa.builder import SikuwaBuilder, build_project, clean_project, sync_project
from sikuwa.log import get_logger, LogLevel
from sikuwa.i18n import _
# 全局日志器
logger = get_logger("sikuwa.cli")
@click.group()
@click.version_option(version="1.2.0", prog_name="sikuwa")
def cli():
"""
Sikuwa - Python 项目打包工具
基于 Nuitka 的跨平台 Python 应用构建工具
"""
pass
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'-p', '--platform',
type=click.Choice(['windows', 'linux', 'macos'], case_sensitive=False),
help='目标平台 (默认: 构建所有平台)'
)
@click.option(
'-m', '--mode',
type=click.Choice(['nuitka', 'native'], case_sensitive=False),
help='编译模式: nuitka (默认) | native (Python→C/C++→GCC→dll/so+exe)'
)
@click.option(
'-v', '--verbose',
is_flag=True,
help=_('详细输出模式')
)
@click.option(
'-f', '--force',
is_flag=True,
help=_('强制重新构建')
)
@click.option(
'--keep-c-source',
is_flag=True,
help='保留生成的 C/C++ 源码 (仅 native 模式)'
)
def build(config: Optional[str], platform: Optional[str], mode: Optional[str],
verbose: bool, force: bool, keep_c_source: bool):
"""
构建项目
支持两种编译模式:
\b
1. nuitka (默认): 使用 Nuitka 编译器
2. native: Python → C/C++ → GCC/G++ → dll/so + exe
生成通用动态链接库,不使用 Python 专用格式 (.pyd)
示例:
sikuwa build # 使用 Nuitka 构建
sikuwa build -m native # 使用原生编译器构建
sikuwa build -m native -v # 原生编译 + 详细输出
sikuwa build -p windows # 只构建 Windows 平台
sikuwa build -c my_config.toml # 使用指定配置文件
"""
try:
# 加载配置
logger.info_operation("加载构建配置...")
build_config = ConfigManager.load_config(config)
# 如果命令行指定了编译模式,覆盖配置文件中的设置
if mode:
build_config.compiler_mode = mode
if mode == 'native' and keep_c_source:
build_config.native_options.keep_c_source = True
# 验证配置
logger.info_operation("验证配置...")
build_config.validate()
# 执行构建
logger.info_operation(f"开始构建项目: {build_config.project_name}")
logger.info_operation(f"编译模式: {build_config.compiler_mode.upper()}")
success = build_project(
config=build_config,
platform=platform,
verbose=verbose,
force=force
)
if success:
click.echo("\n[OK] 构建成功完成!", err=False)
sys.exit(0)
else:
click.echo("\n[FAIL] 构建失败!", err=True)
sys.exit(1)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
click.echo("\n提示: 使用 'sikuwa init' 创建配置文件", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 构建失败: {e}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'-v', '--verbose',
is_flag=True,
help=_('详细输出模式')
)
def clean(config: Optional[str], verbose: bool):
"""
清理构建文件
删除构建过程中生成的所有文件和目录
示例:
sikuwa clean # 清理构建文件
sikuwa clean -v # 详细输出
"""
try:
# 加载配置
logger.info_operation("加载配置...")
build_config = ConfigManager.load_config(config)
# 执行清理
logger.info_operation("开始清理构建文件...")
success = clean_project(
config=build_config,
verbose=verbose
)
if success:
click.echo("\n[OK] 清理完成!", err=False)
sys.exit(0)
else:
click.echo("\n[FAIL] 清理失败!", err=True)
sys.exit(1)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 清理失败: {e}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
@cli.command()
@click.option(
'-o', '--output',
default='sikuwa.toml',
help='输出配置文件名 (默认: sikuwa.toml)'
)
@click.option(
'--force',
is_flag=True,
help=_('覆盖已存在的文件')
)
def init(output: str, force: bool):
"""
初始化项目配置
创建默认的 sikuwa.toml 配置文件
示例:
sikuwa init # 创建 sikuwa.toml
sikuwa init -o custom.toml # 创建自定义配置文件
sikuwa init --force # 强制覆盖已存在的文件
"""
try:
output_path = Path(output)
# 检查文件是否已存在
if output_path.exists() and not force:
click.echo(f"[WARN] 配置文件已存在: {output}", err=True)
click.echo("使用 --force 选项强制覆盖", err=True)
sys.exit(1)
# 创建配置文件
logger.info_operation(f"创建配置文件: {output}")
create_config(output)
click.echo(f"\n[OK] 配置文件已创建: {output}")
click.echo("\n下一步:")
click.echo(" 1. 编辑 sikuwa.toml配置项目信息")
click.echo(" 2. 运行 'sikuwa build' 开始构建")
sys.exit(0)
except Exception as e:
click.echo(f"[FAIL] 创建配置文件失败: {e}", err=True)
sys.exit(1)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
def info(config: Optional[str]):
"""
显示项目信息
显示当前项目的配置信息
示例:
sikuwa info # 显示项目信息
sikuwa info -c custom.toml # 显示指定配置文件的信息
"""
try:
# 加载配置
build_config = ConfigManager.load_config(config)
# 显示项目信息
click.echo("\n" + "=" * 70)
click.echo(f"项目信息: {build_config.project_name}")
click.echo("=" * 70)
click.echo(f"\n基础信息:")
click.echo(f" 项目名称: {build_config.project_name}")
click.echo(f" 版本: {build_config.version}")
if build_config.description:
click.echo(f" 描述: {build_config.description}")
if build_config.author:
click.echo(f" 作者: {build_config.author}")
click.echo(f"\n构建配置:")
click.echo(f" 入口文件: {build_config.main_script}")
click.echo(f" 源代码目录: {build_config.src_dir}")
click.echo(f" 输出目录: {build_config.output_dir}")
click.echo(f" 构建目录: {build_config.build_dir}")
click.echo(f" 目标平台: {', '.join(build_config.platforms)}")
click.echo(f"\nNuitka 选项:")
click.echo(f" Standalone: {build_config.nuitka_options.standalone}")
click.echo(f" OneFile: {build_config.nuitka_options.onefile}")
click.echo(f" Follow Imports: {build_config.nuitka_options.follow_imports}")
click.echo(f" Show Progress: {build_config.nuitka_options.show_progress}")
click.echo(f" Enable Console: {build_config.nuitka_options.enable_console}")
if build_config.nuitka_options.include_packages:
click.echo(f"\n包含的包:")
for pkg in build_config.nuitka_options.include_packages:
click.echo(f" - {pkg}")
if build_config.resources:
click.echo(f"\n资源文件:")
for resource in build_config.resources:
click.echo(f" - {resource}")
click.echo("\n" + "=" * 70 + "\n")
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 读取配置失败: {e}", err=True)
sys.exit(1)
@cli.command()
def version():
"""
显示版本信息
"""
click.echo("\nSikuwa v1.2.0")
click.echo("Python 项目打包工具")
click.echo("基于 Nuitka 的跨平台构建系统")
click.echo("\nGitHub: https://github.com/FORGE24/Sikuwa/")
click.echo("文档: https://www.sanrol-cloud.top\n")
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
def validate(config: Optional[str]):
"""
验证配置文件
检查配置文件是否正确
示例:
sikuwa validate # 验证默认配置文件
sikuwa validate -c custom.toml # 验证指定配置文件
"""
try:
# 加载配置
logger.info_operation("加载配置文件...")
build_config = ConfigManager.load_config(config)
# 验证配置
logger.info_operation("验证配置...")
build_config.validate()
click.echo("\n[OK] 配置文件有效!")
# 显示摘要信息
click.echo(f"\n项目: {build_config.project_name}")
click.echo(f"版本: {build_config.version}")
click.echo(f"入口: {build_config.main_script}")
click.echo(f"平台: {', '.join(build_config.platforms)}\n")
sys.exit(0)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
sys.exit(1)
except ValueError as e:
click.echo(f"[FAIL] 配置验证失败: {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 验证失败: {e}", err=True)
sys.exit(1)
@cli.command()
def doctor():
"""
检查构建环境
检查系统环境和依赖项是否满足构建要求
"""
import subprocess
import platform
click.echo("\n" + "=" * 70)
click.echo("Sikuwa 环境诊断")
click.echo("=" * 70)
# 检查 Python 版本
click.echo("\n[1] Python 环境")
python_version = sys.version_info
click.echo(f" 版本: {python_version.major}.{python_version.minor}.{python_version.micro}")
click.echo(f" 路径: {sys.executable}")
if python_version.major < 3 or (python_version.major == 3 and python_version.minor < 7):
click.echo(" [FAIL] Python 版本过低,需要 3.7+", err=True)
else:
click.echo(" [OK] Python 版本满足要求")
# 检查操作系统
click.echo("\n[2] 操作系统")
os_name = platform.system()
os_version = platform.version()
click.echo(f" 系统: {os_name}")
click.echo(f" 版本: {os_version}")
click.echo(f" 架构: {platform.machine()}")
# 检查 Nuitka
click.echo("\n[3] Nuitka")
try:
result = subprocess.run(
["nuitka3", "--version"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
version = result.stdout.strip().split('\n')[0]
click.echo(f" [OK] 已安装: {version}")
else:
click.echo(" [FAIL] Nuitka 未正确安装", err=True)
except FileNotFoundError:
click.echo(" [FAIL] Nuitka 未安装", err=True)
click.echo(" 安装命令: pip install nuitka")
except Exception as e:
click.echo(f" [WARN] 检查 Nuitka 时出错: {e}", err=True)
# 检查编译器 (Windows)
if os_name == "Windows":
click.echo("\n[4] C 编译器 (Windows)")
# 检查 MinGW
try:
result = subprocess.run(
["gcc", "--version"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
version = result.stdout.strip().split('\n')[0]
click.echo(f" [OK] GCC 已安装: {version}")
else:
click.echo(" [WARN] GCC 未找到", err=True)
except FileNotFoundError:
click.echo(" [WARN] GCC 未安装", err=True)
click.echo(" 推荐安装 MinGW-w64 或 MSVC")
except Exception as e:
click.echo(f" [WARN] 检查 GCC 时出错: {e}", err=True)
# 检查 MSVC
try:
result = subprocess.run(
["cl"],
capture_output=True,
text=True,
timeout=5
)
if "Microsoft" in result.stderr or "Microsoft" in result.stdout:
click.echo(" [OK] MSVC 已安装")
else:
click.echo(" [INFO] MSVC 未找到")
except FileNotFoundError:
click.echo(" [INFO] MSVC 未安装")
except Exception as e:
click.echo(f" [INFO] 检查 MSVC 时出错: {e}")
# 检查编译器 (Linux/macOS)
elif os_name in ["Linux", "Darwin"]:
click.echo(f"\n[4] C 编译器 ({os_name})")
try:
result = subprocess.run(
["gcc", "--version"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
version = result.stdout.strip().split('\n')[0]
click.echo(f" [OK] GCC 已安装: {version}")
else:
click.echo(" [FAIL] GCC 未找到", err=True)
except FileNotFoundError:
click.echo(" [FAIL] GCC 未安装", err=True)
if os_name == "Linux":
click.echo(" 安装命令: sudo apt install gcc # Debian/Ubuntu")
click.echo(" sudo yum install gcc # RedHat/CentOS")
elif os_name == "Darwin":
click.echo(" 安装命令: xcode-select --install")
except Exception as e:
click.echo(f" [WARN] 检查 GCC 时出错: {e}", err=True)
# 检查必需的 Python 包
click.echo("\n[5] Python 依赖包")
required_packages = {
'click': 'CLI 框架',
'tomli': 'TOML 解析器',
'tomli_w': 'TOML 写入器',
'nuitka': 'Python 编译器'
}
for package, description in required_packages.items():
try:
__import__(package)
click.echo(f" [OK] {package:15s} - {description}")
except ImportError:
click.echo(f" [FAIL] {package:15s} - {description} (未安装)", err=True)
# 检查可选包
click.echo("\n[6] 可选依赖包")
optional_packages = {
'ordered_set': _('有序集合支持'),
'zstandard': 'Zstandard 压缩',
}
for package, description in optional_packages.items():
try:
__import__(package)
click.echo(f" [OK] {package:15s} - {description}")
except ImportError:
click.echo(f" [INFO] {package:15s} - {description} (未安装)")
# 总结
click.echo("\n" + "=" * 70)
click.echo(_("诊断完成"))
click.echo("=" * 70)
click.echo("\n如果有 [FAIL] 项,请先解决这些问题后再进行构建。\n")
@cli.command()
@click.argument('query', required=False)
def help_cmd(query: Optional[str]):
"""
显示帮助信息
示例:
sikuwa help # 显示总体帮助
sikuwa help build # 显示 build 命令帮助
sikuwa help config # 显示配置文件帮助
"""
if not query:
# 显示总体帮助
click.echo("\n" + "=" * 70)
click.echo("Sikuwa - Python 项目打包工具")
click.echo("=" * 70)
click.echo("\n常用命令:")
click.echo(" sikuwa init 创建配置文件")
click.echo(" sikuwa build 构建项目")
click.echo(" sikuwa clean 清理构建文件")
click.echo(" sikuwa sync 同步项目依赖")
click.echo(" sikuwa info 显示项目信息")
click.echo(" sikuwa doctor 检查构建环境")
click.echo("\n获取更多帮助:")
click.echo(" sikuwa --help 显示所有命令")
click.echo(" sikuwa <command> --help 显示命令详细帮助")
click.echo(" sikuwa help config 配置文件帮助")
click.echo("\n快速开始:")
click.echo(" 1. sikuwa init # 创建配置文件")
click.echo(" 2. 编辑 sikuwa.toml # 配置项目")
click.echo(" 3. sikuwa sync # 同步项目依赖")
click.echo(" 4. sikuwa build # 构建项目")
click.echo("\n文档: https://www.sanrol-cloud.top")
click.echo("=" * 70 + "\n")
elif query.lower() == "config":
# 配置文件帮助
click.echo("\n" + "=" * 70)
click.echo("Sikuwa 配置文件说明")
click.echo("=" * 70)
click.echo("\n配置文件示例 (sikuwa.toml):\n")
click.echo("""[sikuwa]
project_name = "my_app"
version = "1.0.0"
description = "My Application"
author = "Your Name"
main_script = "main.py"
src_dir = "."
output_dir = "dist"
build_dir = "build"
platforms = ["windows", "linux"]
resources = ["config.json", "data/"]
[sikuwa.nuitka]
standalone = true
onefile = false
follow_imports = true
show_progress = true
enable_console = true
optimize = true
include_packages = ["requests", "numpy"]
include_modules = []
include_data_files = []
include_data_dirs = []
windows_icon = "icon.ico"
windows_company_name = "My Company"
windows_product_name = "My Product"
""")
click.echo("\n主要配置项:")
click.echo(" project_name 项目名称")
click.echo(" main_script 入口文件")
click.echo(" platforms 目标平台 (windows/linux/macos)")
click.echo(" standalone 独立模式")
click.echo(" onefile 单文件模式")
click.echo(" include_packages 包含的 Python 包")
click.echo("\n详细文档: https://www.sanrol-cloud.top")
click.echo("=" * 70 + "\n")
else:
# 显示特定命令的帮助
ctx = click.Context(cli)
cmd = cli.get_command(ctx, query)
if cmd:
click.echo(cmd.get_help(ctx))
else:
click.echo(f"[FAIL] 未知命令: {query}", err=True)
click.echo("使用 'sikuwa --help' 查看所有可用命令", err=True)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'--format',
type=click.Choice(['text', 'json'], case_sensitive=False),
default='text',
help='输出格式 (默认: text)'
)
def show_config(config: Optional[str], format: str):
"""
显示完整配置
以易读的格式显示所有配置选项
示例:
sikuwa show-config # 显示配置 (文本格式)
sikuwa show-config --format json # 显示配置 (JSON 格式)
"""
try:
# 加载配置
build_config = ConfigManager.load_config(config)
if format == 'json':
# JSON 格式输出
import json
config_dict = build_config.to_dict()
click.echo(json.dumps(config_dict, indent=2, ensure_ascii=False))
else:
# 文本格式输出
config_dict = build_config.to_dict()
click.echo("\n" + "=" * 70)
click.echo(_("完整配置"))
click.echo("=" * 70)
def print_dict(d, indent=0):
for key, value in d.items():
if isinstance(value, dict):
click.echo(" " * indent + f"{key}:")
print_dict(value, indent + 1)
elif isinstance(value, list):
click.echo(" " * indent + f"{key}:")
for item in value:
click.echo(" " * (indent + 1) + f"- {item}")
else:
click.echo(" " * indent + f"{key}: {value}")
print_dict(config_dict)
click.echo("=" * 70 + "\n")
sys.exit(0)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 读取配置失败: {e}", err=True)
sys.exit(1)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'-v', '--verbose',
is_flag=True,
help=_('详细输出模式')
)
def sync(config: Optional[str], verbose: bool):
"""
同步项目依赖
自动创建或进入虚拟环境,并安装配置文件中指定的依赖包
示例:
sikuwa sync # 同步依赖
sikuwa sync -v # 详细输出
sikuwa sync -c my_config.toml # 使用指定配置文件
"""
try:
from sikuwa.builder import sync_project
# 加载配置
logger.info_operation("加载构建配置...")
build_config = ConfigManager.load_config(config)
# 验证配置
logger.info_operation("验证配置...")
build_config.validate()
# 执行同步
logger.info_operation(f"开始同步项目依赖: {build_config.project_name}")
success = sync_project(
config=build_config,
verbose=verbose
)
if success:
click.echo("\n[OK] 依赖同步成功完成!", err=False)
sys.exit(0)
else:
click.echo("\n[FAIL] 依赖同步失败!", err=True)
sys.exit(1)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
click.echo("\n提示: 使用 'sikuwa init' 创建配置文件", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 同步失败: {e}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'-v', '--verbose',
is_flag=True,
help='详细输出模式'
)
def build_sequence(config: Optional[str], verbose: bool):
"""
执行编译序列构建
支持按配置文件中定义的项目依赖关系进行拓扑排序,并可选择并行构建
示例:
sikuwa build-sequence # 执行编译序列构建
sikuwa build-sequence -v # 详细输出
sikuwa build-sequence -c my_config.toml # 使用指定配置文件
"""
try:
from sikuwa.builder import build_sequence
# 加载配置
logger.info_operation("加载构建配置...")
build_config = ConfigManager.load_config(config)
# 验证配置
logger.info_operation("验证配置...")
build_config.validate()
# 执行编译序列构建
success = build_sequence(
config=build_config,
verbose=verbose
)
if success:
click.echo("\n[OK] 编译序列构建成功完成!", err=False)
sys.exit(0)
else:
click.echo("\n[FAIL] 编译序列构建失败!", err=True)
sys.exit(1)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
click.echo("\n提示: 使用 'sikuwa init' 创建配置文件", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 构建失败: {e}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
def main():
"""主入口函数"""
try:
cli()
except KeyboardInterrupt:
click.echo("\n\n[WARN] 用户中断操作", err=True)
sys.exit(130)
except Exception as e:
click.echo(f"\n[FAIL] 未预期的错误: {e}", err=True)
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
if __name__ == '__main__':
main()

14
compile_translations.py Normal file
View File

@@ -0,0 +1,14 @@
from babel.messages.pofile import read_po
from babel.messages.mofile import write_mo
from pathlib import Path
po_path = Path('i18n/locales/en_US/LC_MESSAGES/sikuwa.po')
mo_path = po_path.with_suffix('.mo')
with open(po_path, 'rb') as f:
catalog = read_po(f)
with open(mo_path, 'wb') as f:
write_mo(f, catalog)
print(f"Successfully compiled {po_path} to {mo_path}")

771
compiler.py Normal file
View File

@@ -0,0 +1,771 @@
# sikuwa/compiler.py
"""
Sikuwa Native Compiler - Python → C/C++ → GCC/G++ → dll/so + exe
不使用 Python 专用链接库格式,生成通用动态链接库
"""
import subprocess
import shutil
import sys
import os
import tempfile
import hashlib
from pathlib import Path
from typing import Optional, List, Dict, Any, Tuple
from dataclasses import dataclass, field
from datetime import datetime
import traceback
# 兼容扁平结构和包结构的导入
try:
from sikuwa.log import get_logger, PerfTimer, LogLevel
from sikuwa.i18n import _
except ImportError:
from log import get_logger, PerfTimer, LogLevel
from i18n import _
@dataclass
class CompilerConfig:
"""编译器配置"""
# 编译模式
mode: str = "native" # native | cython | cffi
# 编译器选择
cc: str = "gcc" # C 编译器
cxx: str = "g++" # C++ 编译器
# 编译选项
c_flags: List[str] = field(default_factory=lambda: ["-O2", "-fPIC"])
cxx_flags: List[str] = field(default_factory=lambda: ["-O2", "-fPIC", "-std=c++17"])
link_flags: List[str] = field(default_factory=list)
# 输出选项
output_dll: bool = True # 生成 dll/so
output_exe: bool = True # 生成 exe
output_static: bool = False # 生成静态库
# 嵌入 Python
embed_python: bool = True # 嵌入 Python 解释器
python_static: bool = False # 静态链接 Python
# 优化选项
lto: bool = False # Link Time Optimization
strip: bool = True # 剥离符号
# 调试选项
debug: bool = False # 调试模式
keep_c_source: bool = False # 保留生成的 C/C++ 源码
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return {
'mode': self.mode,
'cc': self.cc,
'cxx': self.cxx,
'c_flags': self.c_flags,
'cxx_flags': self.cxx_flags,
'link_flags': self.link_flags,
'output_dll': self.output_dll,
'output_exe': self.output_exe,
'output_static': self.output_static,
'embed_python': self.embed_python,
'python_static': self.python_static,
'lto': self.lto,
'strip': self.strip,
'debug': self.debug,
'keep_c_source': self.keep_c_source,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'CompilerConfig':
"""从字典创建"""
valid_fields = {'mode', 'cc', 'cxx', 'c_flags', 'cxx_flags', 'link_flags',
'output_dll', 'output_exe', 'output_static', 'embed_python',
'python_static', 'lto', 'strip', 'debug', 'keep_c_source'}
filtered = {k: v for k, v in data.items() if k in valid_fields}
return cls(**filtered)
class PythonInfo:
"""Python 环境信息"""
def __init__(self):
self.version = f"{sys.version_info.major}.{sys.version_info.minor}"
self.version_full = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
self.executable = sys.executable
self.prefix = sys.prefix
self.base_prefix = sys.base_prefix
# 获取编译相关路径
self._detect_paths()
def _detect_paths(self):
"""检测 Python 开发路径"""
import sysconfig
self.include_dir = sysconfig.get_path('include')
self.stdlib_dir = sysconfig.get_path('stdlib')
# 获取链接库路径
if sys.platform == 'win32':
self.lib_dir = Path(sys.prefix) / 'libs'
self.lib_name = f"python{sys.version_info.major}{sys.version_info.minor}"
self.dll_name = f"python{sys.version_info.major}{sys.version_info.minor}.dll"
else:
self.lib_dir = Path(sysconfig.get_config_var('LIBDIR') or '/usr/lib')
self.lib_name = sysconfig.get_config_var('LDLIBRARY') or f"python{self.version}"
self.dll_name = f"libpython{self.version}.so"
# 获取编译标志
self.cflags = sysconfig.get_config_var('CFLAGS') or ''
self.ldflags = sysconfig.get_config_var('LDFLAGS') or ''
class NativeCompiler:
"""
原生编译器 - Python → C/C++ → GCC/G++ → dll/so + exe
编译流程:
1. Python 源码 → Cython → C/C++ 源码
2. C/C++ 源码 → GCC/G++ 编译 → 目标文件 (.o)
3. 目标文件 → 链接 → dll/so 动态链接库 + exe 可执行文件
"""
def __init__(self, config: CompilerConfig, verbose: bool = False):
self.config = config
self.verbose = verbose
# 初始化日志
log_level = LogLevel.TRACE_FLOW if verbose else LogLevel.INFO_OPERATION
self.logger = get_logger("sikuwa.compiler", level=log_level)
# Python 环境信息
self.python_info = PythonInfo()
# 工作目录
self.work_dir: Optional[Path] = None
self.c_source_dir: Optional[Path] = None
self.obj_dir: Optional[Path] = None
self.output_dir: Optional[Path] = None
self.logger.info_operation("=" * 70)
self.logger.info_operation(_("初始化原生编译器"))
self.logger.info_operation("=" * 70)
self.logger.debug_config(f"Python {_('版本')}: {self.python_info.version_full}")
self.logger.debug_config(f"C {_('编译器')}: {config.cc}")
self.logger.debug_config(f"C++ {_('编译器')}: {config.cxx}")
self.logger.debug_config(f"{_('编译模式')}: {config.mode}")
def compile_project(
self,
project_name: str,
src_dir: Path,
main_script: str,
output_dir: Path,
platform: str
) -> Dict[str, Path]:
"""
编译整个项目
Returns:
Dict[str, Path]: 生成的文件路径 {'dll': Path, 'exe': Path, ...}
"""
self.logger.info_operation(f"\n{_('开始编译项目')}: {project_name}")
results = {}
with PerfTimer(_("完整编译流程"), self.logger):
try:
# Step 1: 设置工作目录
self._setup_work_dirs(output_dir, platform)
# Step 2: 收集 Python 源文件
py_files = self._collect_python_files(src_dir)
self.logger.info_operation(f" {_('发现')} {len(py_files)} {_('个 Python 文件')}")
# Step 3: Python → C/C++ 转换
self.logger.info_operation(f"\n[1/4] Python → C/C++ {_('转换')}...")
with PerfTimer("Python → C/C++", self.logger):
c_files = self._python_to_c(py_files, src_dir, main_script)
self.logger.info_operation(f" [OK] {_('生成')} {len(c_files)} {_('个 C/C++ 文件')}")
# Step 4: C/C++ → 目标文件
self.logger.info_operation(f"\n[2/4] C/C++ → {_('目标文件')}...")
with PerfTimer("C/C++ → .o", self.logger):
obj_files = self._compile_c_files(c_files)
self.logger.info_operation(f" [OK] {_('生成')} {len(obj_files)} {_('个目标文件')}")
# Step 5: 链接生成 dll/so
if self.config.output_dll:
self.logger.info_operation(f"\n[3/4] {_('链接生成动态库')}...")
with PerfTimer(_("链接 dll/so"), self.logger):
dll_path = self._link_shared_library(obj_files, project_name, platform)
results['dll'] = dll_path
self.logger.info_operation(f" [OK] {dll_path.name}")
# Step 6: 链接生成 exe
if self.config.output_exe:
self.logger.info_operation(f"\n[4/4] {_('链接生成可执行文件')}...")
with PerfTimer(_("链接 exe"), self.logger):
exe_path = self._link_executable(obj_files, project_name, platform)
results['exe'] = exe_path
self.logger.info_operation(f" [OK] {exe_path.name}")
# Step 7: 复制运行时依赖
self.logger.info_operation(f"\n{_('复制运行时依赖')}...")
with PerfTimer(_("复制依赖"), self.logger):
self._copy_runtime_deps(platform)
# 清理临时文件
if not self.config.keep_c_source:
self._cleanup()
self.logger.info_operation(f"\n[OK] {_('编译完成')}!")
return results
except Exception as e:
self.logger.error_minimal(f"[FAIL] {_('编译失败')}: {e}")
self.logger.debug_detail(traceback.format_exc())
raise
def _setup_work_dirs(self, output_dir: Path, platform: str):
"""设置工作目录"""
self.output_dir = output_dir / f"native-{platform}"
self.work_dir = output_dir / f".native_build_{platform}"
self.c_source_dir = self.work_dir / "c_source"
self.obj_dir = self.work_dir / "obj"
for d in [self.output_dir, self.work_dir, self.c_source_dir, self.obj_dir]:
d.mkdir(parents=True, exist_ok=True)
self.logger.trace_io(f" {_('创建目录')}: {d}")
def _collect_python_files(self, src_dir: Path) -> List[Path]:
"""收集 Python 源文件"""
py_files = []
for py_file in src_dir.rglob("*.py"):
if py_file.is_file() and '__pycache__' not in str(py_file):
py_files.append(py_file)
self.logger.trace_io(f" + {py_file.relative_to(src_dir)}")
return py_files
def _python_to_c(
self,
py_files: List[Path],
src_dir: Path,
main_script: str
) -> List[Tuple[Path, bool]]:
"""
Python → C/C++ 转换
使用 Cython 将 Python 代码转换为 C/C++ 代码
Returns:
List[Tuple[Path, bool]]: [(c_file_path, is_main), ...]
"""
c_files = []
main_file = (src_dir / main_script).resolve()
# 检查 Cython 是否可用
try:
import Cython
from Cython.Compiler import Main as CythonMain
from Cython.Compiler.Options import CompilationOptions
cython_available = True
self.logger.debug_config(f"Cython {_('版本')}: {Cython.__version__}")
except ImportError:
cython_available = False
self.logger.warn_minor("Cython {_('未安装')}, {_('使用内置转换器')}")
for py_file in py_files:
relative_path = py_file.relative_to(src_dir)
c_file = self.c_source_dir / relative_path.with_suffix('.c')
c_file.parent.mkdir(parents=True, exist_ok=True)
is_main = py_file.resolve() == main_file
if cython_available:
# 使用 Cython 转换
self._cython_compile(py_file, c_file)
else:
# 使用内置简易转换器
self._builtin_convert(py_file, c_file, is_main)
c_files.append((c_file, is_main))
self.logger.trace_io(f" {py_file.name}{c_file.name}")
# 生成主入口 C 文件(如果嵌入 Python
if self.config.embed_python:
main_c = self._generate_main_wrapper(main_script, src_dir)
c_files.append((main_c, True))
return c_files
def _cython_compile(self, py_file: Path, c_file: Path):
"""使用 Cython 编译"""
cmd = [
sys.executable, "-m", "cython",
"-3", # Python 3 语法
"--embed" if self.config.embed_python else "",
"-o", str(c_file),
str(py_file)
]
cmd = [c for c in cmd if c] # 移除空字符串
self.logger.trace_io(f"Cython: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
self.logger.error_minimal(f"Cython {_('转换失败')}: {py_file}")
self.logger.debug_detail(result.stderr)
raise RuntimeError(f"Cython failed: {result.stderr}")
def _builtin_convert(self, py_file: Path, c_file: Path, is_main: bool):
"""
内置简易转换器 - 生成 C 包装代码
这是一个简化的方案:将 Python 代码作为字符串嵌入到 C 程序中,
运行时通过 Python C API 执行
"""
py_code = py_file.read_text(encoding='utf-8')
# 转义字符串
escaped_code = py_code.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n"\n"')
module_name = py_file.stem
c_code = f'''
/* Auto-generated by Sikuwa Native Compiler */
/* Source: {py_file.name} */
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static const char* {module_name}_source =
"{escaped_code}";
PyObject* PyInit_{module_name}(void) {{
PyObject* module = PyModule_Create(&{module_name}_def);
if (module == NULL) return NULL;
PyObject* code = Py_CompileString({module_name}_source, "{py_file.name}", Py_file_input);
if (code == NULL) {{
Py_DECREF(module);
return NULL;
}}
PyObject* result = PyEval_EvalCode(code, PyModule_GetDict(module), PyModule_GetDict(module));
Py_DECREF(code);
if (result == NULL) {{
Py_DECREF(module);
return NULL;
}}
Py_DECREF(result);
return module;
}}
static PyModuleDef {module_name}_def = {{
PyModuleDef_HEAD_INIT,
"{module_name}",
NULL,
-1,
NULL
}};
'''
c_file.write_text(c_code, encoding='utf-8')
def _generate_main_wrapper(self, main_script: str, src_dir: Path) -> Path:
"""生成主入口 C 包装文件"""
main_c = self.c_source_dir / "_sikuwa_main.c"
# 读取主脚本
main_py = src_dir / main_script
main_code = main_py.read_text(encoding='utf-8')
escaped_code = main_code.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n"\n"')
c_code = f'''
/* Sikuwa Native Compiler - Main Entry Point */
/* Generated: {datetime.now().isoformat()} */
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#define PATH_SEP "\\\\"
#else
#include <unistd.h>
#define PATH_SEP "/"
#endif
static const char* main_source =
"{escaped_code}";
/* 获取可执行文件所在目录 */
static void get_exe_dir(char* buffer, size_t size) {{
#ifdef _WIN32
GetModuleFileNameA(NULL, buffer, (DWORD)size);
char* last_sep = strrchr(buffer, '\\\\');
if (last_sep) *last_sep = '\\0';
#else
ssize_t len = readlink("/proc/self/exe", buffer, size - 1);
if (len != -1) {{
buffer[len] = '\\0';
char* last_sep = strrchr(buffer, '/');
if (last_sep) *last_sep = '\\0';
}} else {{
buffer[0] = '.';
buffer[1] = '\\0';
}}
#endif
}}
int main(int argc, char* argv[]) {{
char exe_dir[4096];
get_exe_dir(exe_dir, sizeof(exe_dir));
/* 设置 Python Home (如果存在 bundled Python) */
char python_home[4096];
snprintf(python_home, sizeof(python_home), "%s%spython", exe_dir, PATH_SEP);
#ifdef _WIN32
wchar_t w_python_home[4096];
MultiByteToWideChar(CP_UTF8, 0, python_home, -1, w_python_home, 4096);
/* 检查是否存在 bundled Python */
char python_dll[4096];
snprintf(python_dll, sizeof(python_dll), "%s\\\\python{self.python_info.version.replace('.', '')}.dll", exe_dir);
FILE* f = fopen(python_dll, "rb");
if (f) {{
fclose(f);
Py_SetPythonHome(w_python_home);
}}
#endif
/* 初始化 Python */
Py_Initialize();
if (!Py_IsInitialized()) {{
fprintf(stderr, "Error: Failed to initialize Python\\n");
return 1;
}}
/* 设置 sys.argv */
wchar_t** wargv = (wchar_t**)malloc(sizeof(wchar_t*) * argc);
for (int i = 0; i < argc; i++) {{
size_t len = strlen(argv[i]) + 1;
wargv[i] = (wchar_t*)malloc(sizeof(wchar_t) * len);
mbstowcs(wargv[i], argv[i], len);
}}
PySys_SetArgvEx(argc, wargv, 0);
/* 添加当前目录到 sys.path */
PyObject* sys_path = PySys_GetObject("path");
PyObject* exe_dir_obj = PyUnicode_FromString(exe_dir);
PyList_Insert(sys_path, 0, exe_dir_obj);
Py_DECREF(exe_dir_obj);
/* 执行主程序 */
int result = PyRun_SimpleString(main_source);
/* 清理 */
for (int i = 0; i < argc; i++) {{
free(wargv[i]);
}}
free(wargv);
Py_Finalize();
return result;
}}
'''
main_c.write_text(c_code, encoding='utf-8')
self.logger.debug_detail(f"{_('生成主入口文件')}: {main_c}")
return main_c
def _compile_c_files(self, c_files: List[Tuple[Path, bool]]) -> List[Path]:
"""编译 C/C++ 文件为目标文件"""
obj_files = []
for c_file, is_main in c_files:
obj_file = self.obj_dir / c_file.with_suffix('.o').name
# 选择编译器
if c_file.suffix in ['.cpp', '.cxx', '.cc']:
compiler = self.config.cxx
flags = self.config.cxx_flags.copy()
else:
compiler = self.config.cc
flags = self.config.c_flags.copy()
# 添加 Python 头文件路径
flags.append(f"-I{self.python_info.include_dir}")
# 调试模式
if self.config.debug:
flags.extend(["-g", "-O0"])
# 构建命令
cmd = [compiler] + flags + ["-c", str(c_file), "-o", str(obj_file)]
self.logger.trace_io(f" {c_file.name}{obj_file.name}")
if self.verbose:
self.logger.debug_detail(f" $ {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
self.logger.error_minimal(f"{_('编译失败')}: {c_file.name}")
self.logger.debug_detail(result.stderr)
raise RuntimeError(f"Compilation failed: {c_file.name}\n{result.stderr}")
obj_files.append(obj_file)
return obj_files
def _link_shared_library(
self,
obj_files: List[Path],
project_name: str,
platform: str
) -> Path:
"""链接生成动态链接库 (dll/so)"""
# 确定输出文件名
if platform == 'windows':
dll_name = f"{project_name}.dll"
import_lib = f"{project_name}.lib"
elif platform == 'macos':
dll_name = f"lib{project_name}.dylib"
import_lib = None
else:
dll_name = f"lib{project_name}.so"
import_lib = None
dll_path = self.output_dir / dll_name
# 选择链接器
linker = self.config.cxx # 使用 C++ 链接器
# 构建链接命令
link_flags = self.config.link_flags.copy()
if platform == 'windows':
link_flags.extend(["-shared", f"-Wl,--out-implib,{self.output_dir / import_lib}"])
else:
link_flags.append("-shared")
# 添加 Python 库
link_flags.append(f"-L{self.python_info.lib_dir}")
link_flags.append(f"-l{self.python_info.lib_name}")
# LTO 优化
if self.config.lto:
link_flags.append("-flto")
# 剥离符号
if self.config.strip and not self.config.debug:
link_flags.append("-s")
cmd = [linker] + [str(o) for o in obj_files] + link_flags + ["-o", str(dll_path)]
self.logger.debug_detail(f"$ {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
self.logger.error_minimal(f"{_('链接失败')}: {dll_name}")
self.logger.debug_detail(result.stderr)
raise RuntimeError(f"Linking failed: {dll_name}\n{result.stderr}")
return dll_path
def _link_executable(
self,
obj_files: List[Path],
project_name: str,
platform: str
) -> Path:
"""链接生成可执行文件"""
# 确定输出文件名
if platform == 'windows':
exe_name = f"{project_name}.exe"
else:
exe_name = project_name
exe_path = self.output_dir / exe_name
# 选择链接器
linker = self.config.cxx
# 构建链接命令
link_flags = self.config.link_flags.copy()
# 添加 Python 库
link_flags.append(f"-L{self.python_info.lib_dir}")
link_flags.append(f"-l{self.python_info.lib_name}")
# Windows 特定
if platform == 'windows':
link_flags.extend(["-lws2_32", "-ladvapi32", "-lshell32"])
# Linux 特定
if platform == 'linux':
link_flags.extend(["-lpthread", "-ldl", "-lutil", "-lm"])
# LTO 优化
if self.config.lto:
link_flags.append("-flto")
# 剥离符号
if self.config.strip and not self.config.debug:
link_flags.append("-s")
cmd = [linker] + [str(o) for o in obj_files] + link_flags + ["-o", str(exe_path)]
self.logger.debug_detail(f"$ {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
self.logger.error_minimal(f"{_('链接失败')}: {exe_name}")
self.logger.debug_detail(result.stderr)
raise RuntimeError(f"Linking failed: {exe_name}\n{result.stderr}")
return exe_path
def _copy_runtime_deps(self, platform: str):
"""复制运行时依赖"""
# 复制 Python DLL (如果需要)
if not self.config.python_static and self.config.embed_python:
if platform == 'windows':
python_dll = Path(sys.prefix) / self.python_info.dll_name
if python_dll.exists():
dest = self.output_dir / python_dll.name
shutil.copy2(python_dll, dest)
self.logger.trace_io(f" {_('复制')}: {python_dll.name}")
# 复制 vcruntime
vcruntime = Path(sys.prefix) / "vcruntime140.dll"
if vcruntime.exists():
shutil.copy2(vcruntime, self.output_dir / vcruntime.name)
self.logger.trace_io(f" {_('复制')}: vcruntime140.dll")
elif platform == 'linux':
python_so = self.python_info.lib_dir / self.python_info.dll_name
if python_so.exists():
dest = self.output_dir / python_so.name
shutil.copy2(python_so, dest)
self.logger.trace_io(f" {_('复制')}: {python_so.name}")
# 复制标准库 (如果嵌入 Python)
if self.config.embed_python:
self._copy_stdlib(platform)
def _copy_stdlib(self, platform: str):
"""复制 Python 标准库"""
stdlib_dest = self.output_dir / "python_lib"
stdlib_dest.mkdir(exist_ok=True)
# 复制 zip 格式的标准库 (如果存在)
if platform == 'windows':
stdlib_zip = Path(sys.prefix) / f"python{self.python_info.version.replace('.', '')}.zip"
if stdlib_zip.exists():
shutil.copy2(stdlib_zip, self.output_dir / stdlib_zip.name)
self.logger.trace_io(f" {_('复制')}: {stdlib_zip.name}")
return
# 复制关键标准库模块
essential_modules = [
'os.py', 'sys.py', 'io.py', 'abc.py', 'functools.py',
'collections', 'encodings', 'importlib'
]
stdlib_src = Path(self.python_info.stdlib_dir)
for module in essential_modules:
src = stdlib_src / module
if src.exists():
if src.is_dir():
shutil.copytree(src, stdlib_dest / module, dirs_exist_ok=True)
else:
shutil.copy2(src, stdlib_dest / module)
self.logger.trace_io(f" {_('复制')}: {module}")
def _cleanup(self):
"""清理临时文件"""
if self.work_dir and self.work_dir.exists():
shutil.rmtree(self.work_dir)
self.logger.trace_io(f" {_('清理')}: {self.work_dir}")
def detect_compiler() -> Tuple[str, str]:
"""检测系统中可用的 C/C++ 编译器"""
# Windows 优先检测 MinGW/MSYS2
if sys.platform == 'win32':
compilers = [
('gcc', 'g++'),
('clang', 'clang++'),
('cl', 'cl'), # MSVC
]
else:
compilers = [
('gcc', 'g++'),
('clang', 'clang++'),
]
for cc, cxx in compilers:
try:
result = subprocess.run([cc, '--version'], capture_output=True, timeout=5)
if result.returncode == 0:
return cc, cxx
except (FileNotFoundError, subprocess.TimeoutExpired):
continue
raise RuntimeError("No C/C++ compiler found. Please install GCC, Clang, or MSVC.")
def native_build(
project_name: str,
src_dir: str,
main_script: str,
output_dir: str,
platform: str,
compiler_config: Optional[CompilerConfig] = None,
verbose: bool = False
) -> Dict[str, Path]:
"""
执行原生编译
Args:
project_name: 项目名称
src_dir: 源代码目录
main_script: 主脚本路径
output_dir: 输出目录
platform: 目标平台
compiler_config: 编译器配置
verbose: 详细输出
Returns:
Dict[str, Path]: 生成的文件路径
"""
if compiler_config is None:
cc, cxx = detect_compiler()
compiler_config = CompilerConfig(cc=cc, cxx=cxx)
compiler = NativeCompiler(compiler_config, verbose=verbose)
return compiler.compile_project(
project_name=project_name,
src_dir=Path(src_dir),
main_script=main_script,
output_dir=Path(output_dir),
platform=platform
)

528
config.py Normal file
View File

@@ -0,0 +1,528 @@
# sikuwa/config.py
"""
Sikuwa 配置管理模块
"""
from __future__ import annotations
import sys
from pathlib import Path
from typing import List, Optional, Dict, Any, TYPE_CHECKING
from dataclasses import dataclass, field, asdict
# 修复 tomli 导入,避免 mypyc 问题
if sys.version_info >= (3, 11):
import tomllib
else:
try:
import tomli as tomllib
except ImportError:
raise ImportError(
"Python < 3.11 需要安装 tomli:\n"
" pip install tomli\n"
"或升级到 Python 3.11+"
)
@dataclass
class NuitkaOptions:
"""Nuitka 编译选项"""
# 基础选项
standalone: bool = True
onefile: bool = False
follow_imports: bool = True
show_progress: bool = True
enable_console: bool = True
# 优化选项
optimize: bool = True
lto: bool = False # Link Time Optimization
# 平台特定选项
windows_icon: Optional[str] = None
windows_company_name: Optional[str] = None
windows_product_name: Optional[str] = None
windows_file_version: Optional[str] = None
windows_product_version: Optional[str] = None
macos_app_bundle: bool = False
macos_icon: Optional[str] = None
# 包含/排除选项
include_packages: List[str] = field(default_factory=list)
include_modules: List[str] = field(default_factory=list)
include_data_files: List[str] = field(default_factory=list)
include_data_dirs: List[Dict[str, str]] = field(default_factory=list)
nofollow_imports: List[str] = field(default_factory=list)
nofollow_import_to: List[str] = field(default_factory=list)
# 插件选项
enable_plugins: List[str] = field(default_factory=list)
disable_plugins: List[str] = field(default_factory=list)
# 额外参数
extra_args: List[str] = field(default_factory=list)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
data = asdict(self)
# 过滤掉值为 None 的字段,避免 TOML 序列化错误
filtered_data = {}
for key, value in data.items():
if value is not None:
filtered_data[key] = value
return filtered_data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'NuitkaOptions':
"""从字典创建"""
# 过滤掉不存在的字段
valid_fields = {f.name for f in cls.__dataclass_fields__.values()}
filtered_data = {k: v for k, v in data.items() if k in valid_fields}
return cls(**filtered_data)
@dataclass
class NativeCompilerOptions:
"""原生编译器选项 - Python → C/C++ → GCC/G++ → dll/so + exe"""
# 编译模式
mode: str = "native" # native | cython | cffi
# 编译器选择
cc: str = "gcc" # C 编译器
cxx: str = "g++" # C++ 编译器
# 编译选项
c_flags: List[str] = field(default_factory=lambda: ["-O2", "-fPIC"])
cxx_flags: List[str] = field(default_factory=lambda: ["-O2", "-fPIC", "-std=c++17"])
link_flags: List[str] = field(default_factory=list)
# 输出选项
output_dll: bool = True # 生成 dll/so
output_exe: bool = True # 生成 exe
output_static: bool = False # 生成静态库
# 嵌入 Python
embed_python: bool = True # 嵌入 Python 解释器
python_static: bool = False # 静态链接 Python
# 优化选项
lto: bool = False # Link Time Optimization
strip: bool = True # 剥离符号
# 调试选项
debug: bool = False # 调试模式
keep_c_source: bool = False # 保留生成的 C/C++ 源码
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
return asdict(self)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'NativeCompilerOptions':
"""从字典创建"""
valid_fields = {f.name for f in cls.__dataclass_fields__.values()}
filtered_data = {k: v for k, v in data.items() if k in valid_fields}
return cls(**filtered_data)
@dataclass
class BuildConfig:
"""Sikuwa 构建配置"""
# 项目基础信息
project_name: str
version: str = "1.0.0"
description: str = ""
author: str = ""
# 构建配置
main_script: str = "main.py"
src_dir: str = "."
output_dir: str = "dist"
build_dir: str = "build"
# 目标平台
platforms: List[str] = field(default_factory=lambda: ["windows"])
# 编译模式选择: "nuitka" | "native"
compiler_mode: str = "nuitka"
# Nuitka 选项 (compiler_mode="nuitka" 时使用)
nuitka_options: NuitkaOptions = field(default_factory=NuitkaOptions)
# 原生编译器选项 (compiler_mode="native" 时使用)
native_options: NativeCompilerOptions = field(default_factory=NativeCompilerOptions)
# 资源文件
resources: List[str] = field(default_factory=list)
# Python 环境
python_version: Optional[str] = None
python_path: Optional[str] = None
# 依赖管理
requirements_file: Optional[str] = None
pip_index_url: Optional[str] = None
dependencies: List[str] = field(default_factory=list)
# 编译序列配置
build_sequence: Optional[List[Dict[str, Any]]] = None
sequence_dependencies: Optional[Dict[str, List[str]]] = None
parallel_build: bool = False
max_workers: int = 4
# 钩子脚本
pre_build_script: Optional[str] = None
post_build_script: Optional[str] = None
def validate(self) -> None:
"""验证配置"""
if not self.project_name:
raise ValueError("project_name 不能为空")
# 如果是编译序列配置跳过main_script验证
if not self.build_sequence:
if not self.main_script:
raise ValueError("main_script 不能为空")
valid_platforms = ["windows", "linux", "macos"]
for platform in self.platforms:
if platform not in valid_platforms:
raise ValueError(f"不支持的平台: {platform},有效平台: {valid_platforms}")
# 检查主脚本是否存在
main_file = Path(self.src_dir) / self.main_script
if not main_file.exists():
raise FileNotFoundError(f"主脚本不存在: {main_file}")
def to_dict(self) -> Dict[str, Any]:
"""转换为字典"""
data = asdict(self)
data['nuitka_options'] = self.nuitka_options.to_dict()
data['native_options'] = self.native_options.to_dict()
# 过滤掉值为 None 的字段,避免 TOML 序列化错误
filtered_data = {}
for key, value in data.items():
if value is not None:
filtered_data[key] = value
return filtered_data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'BuildConfig':
"""从字典创建"""
# 提取 nuitka_options
nuitka_data = data.pop('nuitka_options', {})
nuitka_options = NuitkaOptions.from_dict(nuitka_data)
# 提取 native_options
native_data = data.pop('native_options', {})
native_options = NativeCompilerOptions.from_dict(native_data)
# 过滤掉不存在的字段
valid_fields = {f.name for f in cls.__dataclass_fields__.values()}
filtered_data = {k: v for k, v in data.items() if k in valid_fields}
return cls(nuitka_options=nuitka_options, native_options=native_options, **filtered_data)
@classmethod
def from_toml(cls, config_file: str) -> 'BuildConfig':
"""从 TOML 文件加载配置"""
config_path = Path(config_file)
if not config_path.exists():
raise FileNotFoundError(f"配置文件不存在: {config_file}")
try:
with open(config_path, 'rb') as f:
data = tomllib.load(f)
except Exception as e:
raise ValueError(f"解析 TOML 文件失败: {e}")
# 提取 [sikuwa] 部分
if 'sikuwa' not in data:
raise ValueError("配置文件缺少 [sikuwa] 部分")
sikuwa_config = data['sikuwa'].copy()
# 解析 nuitka 选项
nuitka_data = sikuwa_config.pop('nuitka', {})
# 处理可能存在的嵌套 nuitka_options
if 'nuitka_options' in sikuwa_config:
nuitka_data.update(sikuwa_config.pop('nuitka_options'))
nuitka_options = NuitkaOptions.from_dict(nuitka_data)
# 解析原生编译器选项
native_data = sikuwa_config.pop('native', {})
# 处理可能存在的嵌套 native_options
if 'native_options' in sikuwa_config:
native_data.update(sikuwa_config.pop('native_options'))
native_options = NativeCompilerOptions.from_dict(native_data)
# 创建配置对象
config = cls(nuitka_options=nuitka_options, native_options=native_options, **sikuwa_config)
return config
def save_to_toml(self, config_file: str) -> None:
"""保存配置到 TOML 文件"""
# 优先使用 tomli_w
try:
import tomli_w as toml_writer
use_binary = True
except ImportError:
try:
import toml as toml_writer
use_binary = False
except ImportError:
raise ImportError(
"需要安装 'tomli-w''toml' 包以保存 TOML 文件:\n"
" pip install tomli-w\n"
"\n"
" pip install toml"
)
data = {
'sikuwa': self.to_dict()
}
config_path = Path(config_file)
try:
if use_binary:
with open(config_path, 'wb') as f:
toml_writer.dump(data, f)
else:
with open(config_path, 'w', encoding='utf-8') as f:
toml_writer.dump(data, f)
except Exception as e:
raise IOError(f"保存配置文件失败: {e}")
class ConfigManager:
"""配置管理器"""
DEFAULT_CONFIG_FILES = [
"sikuwa.toml",
"pyproject.toml",
".sikuwa.toml"
]
@staticmethod
def find_config() -> Optional[Path]:
"""自动查找配置文件"""
for config_file in ConfigManager.DEFAULT_CONFIG_FILES:
config_path = Path(config_file)
if config_path.exists():
return config_path
return None
@staticmethod
def load_config(config_file: Optional[str] = None) -> BuildConfig:
"""加载配置文件"""
if config_file:
return BuildConfig.from_toml(config_file)
# 自动查找配置文件
config_path = ConfigManager.find_config()
if config_path:
return BuildConfig.from_toml(str(config_path))
raise FileNotFoundError(
"未找到配置文件,请创建以下文件之一:\n " +
"\n ".join(ConfigManager.DEFAULT_CONFIG_FILES) +
"\n\n使用命令创建默认配置:\n sikuwa init"
)
@staticmethod
def create_default_config(output_file: str = "sikuwa.toml") -> None:
"""创建默认配置文件"""
default_config = BuildConfig(
project_name="my_project",
version="1.0.0",
description="My Python Project",
author="",
main_script="main.py",
src_dir=".",
output_dir="dist",
build_dir="build",
platforms=["windows"],
nuitka_options=NuitkaOptions(
standalone=True,
onefile=False,
follow_imports=True,
show_progress=True,
enable_console=True,
optimize=True,
include_packages=[],
nofollow_import_to=[
"numpy",
"pandas",
"matplotlib"
]
),
resources=[],
dependencies=["requests>=2.0.0", "click>=8.0.0"]
)
try:
default_config.save_to_toml(output_file)
print(f"✓ 已创建默认配置文件: {output_file}")
except Exception as e:
print(f"✗ 创建配置文件失败: {e}")
raise
# 便捷函数
def load_config(config_file: Optional[str] = None) -> BuildConfig:
"""加载配置(便捷函数)"""
return ConfigManager.load_config(config_file)
def create_config(output_file: str = "sikuwa.toml") -> None:
"""创建默认配置(便捷函数)"""
ConfigManager.create_default_config(output_file)
def validate_config(config: BuildConfig) -> List[str]:
"""验证配置有效性,返回错误列表"""
errors = []
try:
config.validate()
except Exception as e:
errors.append(str(e))
# 额外检查
src_dir = Path(config.src_dir)
if not src_dir.exists():
errors.append(f"源码目录不存在: {config.src_dir}")
if config.nuitka_options.windows_icon:
icon_path = Path(config.nuitka_options.windows_icon)
if not icon_path.exists():
errors.append(f"图标文件不存在: {config.nuitka_options.windows_icon}")
return errors
"""Sikuwa 配置管理"""
from pathlib import Path
from typing import List, Dict, Optional
class SikuwaConfig:
"""Sikuwa 项目配置"""
def __init__(self, config_path: Path = None):
if config_path is None:
config_path = Path("sikuwa.toml")
self.config_path = config_path
self._load_config()
def _load_config(self):
"""加载配置文件"""
if not self.config_path.exists():
raise FileNotFoundError(f"配置文件不存在: {self.config_path}")
with open(self.config_path, 'rb') as f:
data = tomllib.load(f)
# 基础配置
sikuwa = data.get('sikuwa', {})
self.project_name = sikuwa.get('project_name', 'my_project')
self.version = sikuwa.get('version', '1.0.0')
self.main_script = Path(sikuwa.get('main_script', 'main.py'))
self.src_dir = Path(sikuwa.get('src_dir', '.'))
self.output_dir = Path(sikuwa.get('output_dir', 'dist'))
self.build_dir = Path(sikuwa.get('build_dir', 'build'))
self.platforms = sikuwa.get('platforms', ['windows'])
# Nuitka 配置
nuitka = data.get('sikuwa', {}).get('nuitka', {})
self.standalone = nuitka.get('standalone', True)
self.onefile = nuitka.get('onefile', False)
self.follow_imports = nuitka.get('follow_imports', True)
self.show_progress = nuitka.get('show_progress', True)
self.enable_console = nuitka.get('enable_console', True)
self.include_packages = nuitka.get('include_packages', [])
self.include_data_files = nuitka.get('include_data_files', [])
self.include_data_dirs = nuitka.get('include_data_dirs', []) # 新增
self.extra_args = nuitka.get('extra_args', [])
def __repr__(self):
return f"<SikuwaConfig project={self.project_name} version={self.version}>"
if __name__ == '__main__':
# 测试配置模块
print("Sikuwa Config - 测试模式")
print("=" * 70)
# 创建测试配置
test_config = BuildConfig(
project_name="test_app",
version="0.1.0",
main_script="main.py",
platforms=["windows", "linux"],
nuitka_options=NuitkaOptions(
standalone=True,
onefile=True,
follow_imports=True,
include_packages=["requests", "click"],
windows_icon="icon.ico",
nofollow_import_to=["numpy", "pandas"]
),
resources=["config.json", "data/"]
)
print("\n测试配置对象:")
print(f" 项目名称: {test_config.project_name}")
print(f" 版本: {test_config.version}")
print(f" 目标平台: {test_config.platforms}")
print(f" Standalone: {test_config.nuitka_options.standalone}")
print(f" OneFile: {test_config.nuitka_options.onefile}")
# 测试保存和加载
test_file = "test_sikuwa.toml"
try:
print(f"\n保存配置到: {test_file}")
test_config.save_to_toml(test_file)
print(f"从文件加载配置: {test_file}")
loaded_config = BuildConfig.from_toml(test_file)
print("\n加载的配置:")
print(f" 项目名称: {loaded_config.project_name}")
print(f" 版本: {loaded_config.version}")
print(f" 目标平台: {loaded_config.platforms}")
print(f" 包含包: {loaded_config.nuitka_options.include_packages}")
print(f" 排除包: {loaded_config.nuitka_options.nofollow_import_to}")
print("\n✓ 配置模块测试通过!")
except Exception as e:
print(f"\n✗ 测试失败: {e}")
import traceback
traceback.print_exc()
finally:
# 清理测试文件
import os
if os.path.exists(test_file):
os.remove(test_file)
print(f"\n清理测试文件: {test_file}")

37
cpp_cache/CMakeLists.txt Normal file
View File

@@ -0,0 +1,37 @@
# sikuwa/cpp_cache/CMakeLists.txt
# 使用CMake构建C++智能缓存系统
cmake_minimum_required(VERSION 3.15)
project(SmartCache LANGUAGES CXX)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 寻找Python库
find_package(Python3 REQUIRED COMPONENTS Development)
# 添加Python的include目录
include_directories(${Python3_INCLUDE_DIRS})
# 创建Python扩展模块
add_library(pysmartcache MODULE smart_cache.cpp pysmartcache.cpp)
# 链接Python库
target_link_libraries(pysmartcache PRIVATE ${Python3_LIBRARIES})
# 设置输出目录
set_target_properties(pysmartcache PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
# 如果在Windows上确保输出为.pyd文件
if(WIN32)
set_target_properties(pysmartcache PROPERTIES SUFFIX ".pyd")
endif()
# 创建一个简单的测试可执行文件
add_executable(test_smart_cache test_smart_cache.cpp)
target_link_libraries(test_smart_cache PRIVATE)

241
cpp_cache/__init__.py Normal file
View File

@@ -0,0 +1,241 @@
# sikuwa/cpp_cache/__init__.py
# Python包装器模块用于使用C++实现的智能缓存系统
import os
import sys
import json
import hashlib
from pathlib import Path
# 尝试导入C++扩展模块
try:
from .pysmartcache import (
lru_cache_new,
lru_cache_contains,
lru_cache_put,
lru_cache_get,
lru_cache_remove,
lru_cache_clear
)
cpp_extension_loaded = True
except ImportError as e:
print(f"Warning: pysmartcache C++ extension not found. Using fallback implementation. Error: {e}")
cpp_extension_loaded = False
# Python回退实现
if not cpp_extension_loaded:
class FallbackLRUCache:
"""纯Python实现的LRU缓存"""
def __init__(self, max_size=1000):
self.max_size = max_size
self.cache = {}
self.usage_order = []
def contains(self, key):
return key in self.cache
def put(self, key, value):
if key in self.cache:
# 移动到最近使用
self.usage_order.remove(key)
elif len(self.cache) >= self.max_size:
# 移除最久未使用的
oldest = self.usage_order.pop(0)
del self.cache[oldest]
self.cache[key] = value
self.usage_order.append(key)
return True
def get(self, key):
if key not in self.cache:
return ""
# 移动到最近使用
self.usage_order.remove(key)
self.usage_order.append(key)
return self.cache[key]
def remove(self, key):
if key in self.cache:
del self.cache[key]
self.usage_order.remove(key)
return True
return False
def clear(self):
self.cache.clear()
self.usage_order.clear()
return True
# 模拟C++扩展的函数
def lru_cache_new(max_size=1000):
return FallbackLRUCache(max_size)
def lru_cache_contains(cache, key):
return cache.contains(key)
def lru_cache_put(cache, key, value):
return cache.put(key, value)
def lru_cache_get(cache, key):
return cache.get(key)
def lru_cache_remove(cache, key):
return cache.remove(key)
def lru_cache_clear(cache):
return cache.clear()
# LRUCache类的Python包装器
class LRUCache:
"""LRU (Least Recently Used) 缓存的Python包装器"""
def __init__(self, max_size=1000):
"""创建一个新的LRU缓存"""
self.cache = lru_cache_new(max_size)
def contains(self, key):
"""检查缓存中是否包含指定的键"""
return lru_cache_contains(self.cache, key)
def put(self, key, value):
"""将键值对放入缓存"""
return lru_cache_put(self.cache, key, value)
def get(self, key):
"""从缓存中获取指定键的值"""
return lru_cache_get(self.cache, key)
def remove(self, key):
"""从缓存中移除指定的键"""
return lru_cache_remove(self.cache, key)
def clear(self):
"""清空缓存"""
lru_cache_clear(self.cache)
# 纯Python实现的BuildCache
class BuildCache:
"""构建缓存系统"""
def __init__(self, cache_dir=".cache", max_size=1000000000):
"""创建一个新的构建缓存"""
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(parents=True, exist_ok=True)
self.max_size = max_size
self.cache_file = self.cache_dir / "build_cache.json"
self.cache = self._load_cache()
def _load_cache(self):
"""从文件加载缓存"""
if self.cache_file.exists():
try:
with open(self.cache_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
pass
return {}
def _save_cache(self):
"""保存缓存到文件"""
try:
with open(self.cache_file, 'w', encoding='utf-8') as f:
json.dump(self.cache, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error saving cache: {e}")
def set_cache_strategy(self, strategy):
"""设置缓存策略 ("lru""lfu")"""
# Python实现不支持策略切换这里只是为了兼容接口
pass
def cache_build_result(self, target, command, dependencies, result):
"""缓存构建结果"""
cache_key = self._generate_cache_key(target, command, dependencies)
self.cache[cache_key] = {
"result": result,
"dependencies": dependencies,
"command": command,
"timestamp": os.path.getmtime(__file__)
}
self._save_cache()
return True
def get_cached_build_result(self, target, command, dependencies):
"""获取缓存的构建结果"""
cache_key = self._generate_cache_key(target, command, dependencies)
if cache_key in self.cache:
return self.cache[cache_key]["result"]
return ""
def needs_rebuild(self, target, command, dependencies):
"""检查是否需要重新构建"""
cache_key = self._generate_cache_key(target, command, dependencies)
if cache_key not in self.cache:
return True
# 这里简单地总是返回False因为我们已经生成了包含所有依赖的缓存键
# 在实际实现中,可以检查依赖文件是否有变化
return False
def _generate_cache_key(self, target, command, dependencies):
"""生成缓存键"""
# 合并所有信息生成唯一的缓存键
all_info = f"{target}|{command}|{json.dumps(dependencies, sort_keys=True)}"
return hashlib.sha256(all_info.encode()).hexdigest()
def clean_all_cache(self):
"""清理所有缓存"""
self.cache.clear()
self._save_cache()
return True
def dump_stats(self):
"""打印缓存统计信息"""
print(f"Build Cache Statistics:")
print(f" Cache directory: {self.cache_dir}")
print(f" Number of cached items: {len(self.cache)}")
# 为BuildCache创建类似C++扩展的函数接口
def build_cache_new(cache_dir=".cache", max_size=1000000000):
return BuildCache(cache_dir, max_size)
def build_cache_set_cache_strategy(cache, strategy):
return cache.set_cache_strategy(strategy)
def build_cache_cache_build_result(cache, target, command, dependencies, result):
# 如果dependencies是字符串将其转换为列表
if isinstance(dependencies, str):
dependencies = [dependencies]
return cache.cache_build_result(target, command, dependencies, result)
def build_cache_get_cached_build_result(cache, target, command, dependencies):
# 如果dependencies是字符串将其转换为列表
if isinstance(dependencies, str):
dependencies = [dependencies]
return cache.get_cached_build_result(target, command, dependencies)
def build_cache_needs_rebuild(cache, target, command, dependencies):
# 如果dependencies是字符串将其转换为列表
if isinstance(dependencies, str):
dependencies = [dependencies]
return cache.needs_rebuild(target, command, dependencies)
def build_cache_clean_all_cache(cache):
return cache.clean_all_cache()
def build_cache_dump_build_cache_stats(cache):
return cache.dump_stats()
# 导出所有函数
globals().update({
'build_cache_new': build_cache_new,
'build_cache_set_cache_strategy': build_cache_set_cache_strategy,
'build_cache_cache_build_result': build_cache_cache_build_result,
'build_cache_get_cached_build_result': build_cache_get_cached_build_result,
'build_cache_needs_rebuild': build_cache_needs_rebuild,
'build_cache_clean_all_cache': build_cache_clean_all_cache,
'build_cache_dump_build_cache_stats': build_cache_dump_build_cache_stats
})

View File

@@ -0,0 +1,80 @@
// sikuwa/cpp_cache/pysmartcache.cpp
// Python扩展模块用于集成C++智能缓存系统
#include <Python.h>
#include "smart_cache.h"
#include <string>
// LRUCache类的Python包装器
static PyObject* py_lru_cache_new(PyObject* self, PyObject* args) {
size_t max_size = 1000;
if (!PyArg_ParseTuple(args, "|k", &max_size)) {
return NULL;
}
LRUCache* cache = new LRUCache(max_size);
return PyCapsule_New(cache, "LRUCache", NULL);
}
static void py_lru_cache_dealloc(PyObject* capsule) {
LRUCache* cache = (LRUCache*)PyCapsule_GetPointer(capsule, "LRUCache");
if (cache) {
delete cache;
}
}
static PyObject* py_lru_cache_put(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* key;
const char* value;
if (!PyArg_ParseTuple(args, "Oss", &capsule, &key, &value)) {
return NULL;
}
LRUCache* cache = (LRUCache*)PyCapsule_GetPointer(capsule, "LRUCache");
if (!cache) {
PyErr_SetString(PyExc_RuntimeError, "Invalid LRUCache pointer");
return NULL;
}
bool result = cache->put(key, value);
return PyBool_FromLong(result);
}
static PyObject* py_lru_cache_get(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* key;
if (!PyArg_ParseTuple(args, "Os", &capsule, &key)) {
return NULL;
}
LRUCache* cache = (LRUCache*)PyCapsule_GetPointer(capsule, "LRUCache");
if (!cache) {
PyErr_SetString(PyExc_RuntimeError, "Invalid LRUCache pointer");
return NULL;
}
std::string result = cache->get(key);
return PyUnicode_FromString(result.c_str());
}
// 定义Python模块的方法表
static PyMethodDef pysmartcache_methods[] = {
{"lru_cache_new", py_lru_cache_new, METH_VARARGS, "Create a new LRUCache"},
{"lru_cache_put", py_lru_cache_put, METH_VARARGS, "Put a key-value pair into LRUCache"},
{"lru_cache_get", py_lru_cache_get, METH_VARARGS, "Get a value from LRUCache"},
{NULL, NULL, 0, NULL} // Sentinel
};
// 定义Python模块的初始化函数
static struct PyModuleDef pysmartcache_module = {
PyModuleDef_HEAD_INIT,
"pysmartcache", // 模块名称
"C++ Smart Cache Python Extension", // 模块文档
-1, // 模块状态大小
pysmartcache_methods // 模块方法表
};
PyMODINIT_FUNC PyInit_pysmartcache(void) {
return PyModule_Create(&pysmartcache_module);
}

View File

@@ -0,0 +1,150 @@
// pysmartcache_minimal.cpp
// Minimal Python extension for smart cache system
#include <Python.h>
#include "smart_cache_minimal.h"
// LRUCache functions
static PyObject* py_lru_cache_new(PyObject* self, PyObject* args) {
int max_size = 1000;
if (!PyArg_ParseTuple(args, "|i", &max_size)) {
return NULL;
}
LRUCache* cache = new LRUCache(max_size);
return PyCapsule_New(cache, "LRUCache", NULL);
}
static PyObject* py_lru_cache_put(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* key;
const char* value;
if (!PyArg_ParseTuple(args, "Os|s", &capsule, &key, &value)) {
return NULL;
}
LRUCache* cache = (LRUCache*)PyCapsule_GetPointer(capsule, "LRUCache");
if (cache == NULL) {
return NULL;
}
const char* val = value ? value : "";
bool success = cache->put(key, val);
return PyBool_FromLong(success);
}
static PyObject* py_lru_cache_get(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* key;
if (!PyArg_ParseTuple(args, "Os", &capsule, &key)) {
return NULL;
}
LRUCache* cache = (LRUCache*)PyCapsule_GetPointer(capsule, "LRUCache");
if (cache == NULL) {
return NULL;
}
std::string result = cache->get(key);
if (result.empty()) {
Py_RETURN_NONE;
}
return PyUnicode_FromString(result.c_str());
}
// BuildCache functions
static PyObject* py_build_cache_new(PyObject* self, PyObject* args) {
const char* cache_dir = ".cache";
if (!PyArg_ParseTuple(args, "|s", &cache_dir)) {
return NULL;
}
BuildCache* cache = new BuildCache(cache_dir);
return PyCapsule_New(cache, "BuildCache", NULL);
}
static PyObject* py_build_cache_result(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* target;
const char* command;
const char* result;
if (!PyArg_ParseTuple(args, "Oss|s", &capsule, &target, &command, &result)) {
return NULL;
}
BuildCache* cache = (BuildCache*)PyCapsule_GetPointer(capsule, "BuildCache");
if (cache == NULL) {
return NULL;
}
const char* res = result ? result : "";
bool success = cache->cache_result(target, command, res);
return PyBool_FromLong(success);
}
static PyObject* py_build_cache_get(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* target;
const char* command;
if (!PyArg_ParseTuple(args, "Oss", &capsule, &target, &command)) {
return NULL;
}
BuildCache* cache = (BuildCache*)PyCapsule_GetPointer(capsule, "BuildCache");
if (cache == NULL) {
return NULL;
}
std::string res = cache->get_result(target, command);
if (res.empty()) {
Py_RETURN_NONE;
}
return PyUnicode_FromString(res.c_str());
}
static PyObject* py_build_cache_needs_rebuild(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* target;
const char* command;
if (!PyArg_ParseTuple(args, "Oss", &capsule, &target, &command)) {
return NULL;
}
BuildCache* cache = (BuildCache*)PyCapsule_GetPointer(capsule, "BuildCache");
if (cache == NULL) {
return NULL;
}
bool needs = cache->needs_rebuild(target, command);
return PyBool_FromLong(needs);
}
// Method definitions
static PyMethodDef PySmartCacheMethods[] = {
{"lru_cache_new", py_lru_cache_new, METH_VARARGS, "Create LRUCache"},
{"lru_cache_put", py_lru_cache_put, METH_VARARGS, "Put to LRUCache"},
{"lru_cache_get", py_lru_cache_get, METH_VARARGS, "Get from LRUCache"},
{"build_cache_new", py_build_cache_new, METH_VARARGS, "Create BuildCache"},
{"build_cache_result", py_build_cache_result, METH_VARARGS, "Cache build result"},
{"build_cache_get", py_build_cache_get, METH_VARARGS, "Get cached build result"},
{"build_cache_needs_rebuild", py_build_cache_needs_rebuild, METH_VARARGS, "Check if rebuild needed"},
{NULL, NULL, 0, NULL}
};
// Module definition
static struct PyModuleDef pysmartcachemodule = {
PyModuleDef_HEAD_INIT,
"pysmartcache",
"Sikuwa Smart Cache Python Extension",
-1,
PySmartCacheMethods
};
// Module initialization
PyMODINIT_FUNC PyInit_pysmartcache(void) {
return PyModule_Create(&pysmartcachemodule);
}

View File

@@ -0,0 +1,221 @@
// sikuwa/cpp_cache/pysmartcache_simple.cpp
// 简化版智能缓存系统的Python扩展
#include <Python.h>
#include "smart_cache_simple.h"
// 为LRUCache和BuildCache创建Python对象类型
// 简单的Python扩展仅提供基本功能
// LRUCache相关函数
static PyObject* py_lru_cache_new(PyObject* self, PyObject* args) {
int max_size = 1000;
if (!PyArg_ParseTuple(args, "|i", &max_size)) {
return nullptr;
}
LRUCache* cache = new LRUCache(max_size);
return PyCapsule_New(cache, "LRUCache", [](PyObject* capsule) {
LRUCache* cache = static_cast<LRUCache*>(PyCapsule_GetPointer(capsule, "LRUCache"));
delete cache;
});
}
static PyObject* py_lru_cache_put(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* key;
const char* value;
if (!PyArg_ParseTuple(args, "Os|s", &capsule, &key, &value)) {
return nullptr;
}
LRUCache* cache = static_cast<LRUCache*>(PyCapsule_GetPointer(capsule, "LRUCache"));
if (cache == nullptr) {
return nullptr;
}
bool success = cache->put(key, value ? value : "");
return PyBool_FromLong(success);
}
static PyObject* py_lru_cache_get(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* key;
if (!PyArg_ParseTuple(args, "Os", &capsule, &key)) {
return nullptr;
}
LRUCache* cache = static_cast<LRUCache*>(PyCapsule_GetPointer(capsule, "LRUCache"));
if (cache == nullptr) {
return nullptr;
}
std::string result = cache->get(key);
if (result.empty()) {
Py_RETURN_NONE;
}
return PyUnicode_FromString(result.c_str());
}
// 构建缓存相关函数
static PyObject* py_build_cache_new(PyObject* self, PyObject* args) {
const char* cache_dir = ".cache";
int max_size = 1000000000;
if (!PyArg_ParseTuple(args, "|si", &cache_dir, &max_size)) {
return nullptr;
}
BuildCache* cache = new BuildCache(cache_dir, max_size);
return PyCapsule_New(cache, "BuildCache", [](PyObject* capsule) {
BuildCache* cache = static_cast<BuildCache*>(PyCapsule_GetPointer(capsule, "BuildCache"));
delete cache;
});
}
static PyObject* py_cache_build_result(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* target;
const char* command;
PyObject* dependencies_obj;
const char* result;
if (!PyArg_ParseTuple(args, "OsOs|s", &capsule, &target, &command, &dependencies_obj, &result)) {
return nullptr;
}
BuildCache* cache = static_cast<BuildCache*>(PyCapsule_GetPointer(capsule, "BuildCache"));
if (cache == nullptr) {
return nullptr;
}
// Convert Python list to C++ vector
std::vector<std::string> dependencies;
if (!PyList_Check(dependencies_obj)) {
PyErr_SetString(PyExc_TypeError, "dependencies must be a list");
return nullptr;
}
Py_ssize_t len = PyList_Size(dependencies_obj);
for (Py_ssize_t i = 0; i < len; i++) {
PyObject* item = PyList_GetItem(dependencies_obj, i);
if (!PyUnicode_Check(item)) {
PyErr_SetString(PyExc_TypeError, "dependencies must contain strings");
return nullptr;
}
dependencies.push_back(PyUnicode_AsUTF8(item));
}
bool success = cache->cache_build_result(target, command, dependencies, result ? result : "");
return PyBool_FromLong(success);
}
static PyObject* py_get_cached_build_result(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* target;
const char* command;
PyObject* dependencies_obj;
if (!PyArg_ParseTuple(args, "OsOs", &capsule, &target, &command, &dependencies_obj)) {
return nullptr;
}
BuildCache* cache = static_cast<BuildCache*>(PyCapsule_GetPointer(capsule, "BuildCache"));
if (cache == nullptr) {
return nullptr;
}
// Convert Python list to C++ vector
std::vector<std::string> dependencies;
if (!PyList_Check(dependencies_obj)) {
PyErr_SetString(PyExc_TypeError, "dependencies must be a list");
return nullptr;
}
Py_ssize_t len = PyList_Size(dependencies_obj);
for (Py_ssize_t i = 0; i < len; i++) {
PyObject* item = PyList_GetItem(dependencies_obj, i);
if (!PyUnicode_Check(item)) {
PyErr_SetString(PyExc_TypeError, "dependencies must contain strings");
return nullptr;
}
dependencies.push_back(PyUnicode_AsUTF8(item));
}
std::string cached_result = cache->get_cached_build_result(target, command, dependencies);
if (cached_result.empty()) {
Py_RETURN_NONE;
}
return PyUnicode_FromString(cached_result.c_str());
}
static PyObject* py_needs_rebuild(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* target;
const char* command;
PyObject* dependencies_obj;
if (!PyArg_ParseTuple(args, "OsOs", &capsule, &target, &command, &dependencies_obj)) {
return nullptr;
}
BuildCache* cache = static_cast<BuildCache*>(PyCapsule_GetPointer(capsule, "BuildCache"));
if (cache == nullptr) {
return nullptr;
}
// Convert Python list to C++ vector
std::vector<std::string> dependencies;
if (!PyList_Check(dependencies_obj)) {
PyErr_SetString(PyExc_TypeError, "dependencies must be a list");
return nullptr;
}
Py_ssize_t len = PyList_Size(dependencies_obj);
for (Py_ssize_t i = 0; i < len; i++) {
PyObject* item = PyList_GetItem(dependencies_obj, i);
if (!PyUnicode_Check(item)) {
PyErr_SetString(PyExc_TypeError, "dependencies must contain strings");
return nullptr;
}
dependencies.push_back(PyUnicode_AsUTF8(item));
}
bool needs = cache->needs_rebuild(target, command, dependencies);
return PyBool_FromLong(needs);
}
// 模块方法定义
static PyMethodDef PySmartCacheMethods[] = {
// LRUCache方法
{"lru_cache_new", py_lru_cache_new, METH_VARARGS, "Create a new LRUCache instance"},
{"lru_cache_put", py_lru_cache_put, METH_VARARGS, "Put a key-value pair into the LRUCache"},
{"lru_cache_get", py_lru_cache_get, METH_VARARGS, "Get a value from the LRUCache"},
// BuildCache方法
{"build_cache_new", py_build_cache_new, METH_VARARGS, "Create a new BuildCache instance"},
{"cache_build_result", py_cache_build_result, METH_VARARGS, "Cache a build result"},
{"get_cached_build_result", py_get_cached_build_result, METH_VARARGS, "Get a cached build result"},
{"needs_rebuild", py_needs_rebuild, METH_VARARGS, "Check if a build needs to be redone"},
{nullptr, nullptr, 0, nullptr}
};
// 模块定义
static struct PyModuleDef pysmartcachemodule = {
PyModuleDef_HEAD_INIT,
"pysmartcache",
"Sikuwa Smart Cache Python Extension",
-1,
PySmartCacheMethods
};
// 模块初始化函数
PyMODINIT_FUNC PyInit_pysmartcache(void) {
return PyModule_Create(&pysmartcachemodule);
}

30
cpp_cache/setup.py Normal file
View File

@@ -0,0 +1,30 @@
# sikuwa/cpp_cache/setup.py
# 用于编译和安装C++智能缓存扩展模块的setup文件
from setuptools import setup, Extension
import sys
# 获取Python的include目录
py_include_dirs = [sys.prefix + '/include']
# 定义扩展模块
smart_cache_extension = Extension(
'pysmartcache', # 扩展模块名称
sources=['smart_cache_minimal.cpp', 'pysmartcache_minimal.cpp'], # 源文件
include_dirs=[".", *py_include_dirs], # 包含目录
language='c++', # 使用C++
extra_compile_args=['/STD:c++17'], # 编译参数
)
# 设置setup配置
setup(
name='sikuwa_cpp_cache',
version='0.1',
description='C++ Smart Cache System for Sikuwa',
author='Sikuwa Team',
author_email='',
packages=['sikuwa.cpp_cache'],
package_dir={'sikuwa.cpp_cache': '.'},
ext_modules=[smart_cache_extension],
zip_safe=False,
)

393
cpp_cache/smart_cache.cpp Normal file
View File

@@ -0,0 +1,393 @@
// sikuwa/cpp_cache/smart_cache.cpp
// 智能缓存策略和构建缓存系统实现
#include "smart_cache.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstddef>
#include <filesystem>
#include <functional>
// LRU Cache Implementation
LRUCache::LRUCache(size_t max_size) : max_size_(max_size) {
}
LRUCache::~LRUCache() {
clear();
}
bool LRUCache::contains(const std::string& key) const {
std::lock_guard<std::mutex> lock(mutex_);
return cache_.find(key) != cache_.end();
}
bool LRUCache::put(const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(mutex_);
// Check if key already exists
auto it = cache_.find(key);
if (it != cache_.end()) {
// Update value and move to front
it->second.first = value;
usage_order_.erase(it->second.second);
usage_order_.push_front(key);
it->second.second = usage_order_.begin();
return true;
}
// Check if cache is full
if (cache_.size() >= max_size_) {
// Remove least recently used item
std::string lru_key = usage_order_.back();
usage_order_.pop_back();
cache_.erase(lru_key);
}
// Add new item
usage_order_.push_front(key);
cache_[key] = std::make_pair(value, usage_order_.begin());
return true;
}
std::string LRUCache::get(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it == cache_.end()) {
return "";
}
// Move accessed key to front
usage_order_.erase(it->second.second);
usage_order_.push_front(key);
it->second.second = usage_order_.begin();
return it->second.first;
}
bool LRUCache::remove(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it == cache_.end()) {
return false;
}
usage_order_.erase(it->second.second);
cache_.erase(it);
return true;
}
void LRUCache::clear() {
std::lock_guard<std::mutex> lock(mutex_);
cache_.clear();
usage_order_.clear();
}
size_t LRUCache::size() const {
std::lock_guard<std::mutex> lock(mutex_);
return cache_.size();
}
size_t LRUCache::max_size() const {
return max_size_;
}
void LRUCache::set_max_size(size_t max_size) {
std::lock_guard<std::mutex> lock(mutex_);
max_size_ = max_size;
// Evict items if necessary
while (cache_.size() > max_size_) {
std::string lru_key = usage_order_.back();
usage_order_.pop_back();
cache_.erase(lru_key);
}
}
void LRUCache::dump_cache_stats() const {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "LRU Cache Statistics:" << std::endl;
std::cout << " Current size: " << cache_.size() << std::endl;
std::cout << " Maximum size: " << max_size_ << std::endl;
std::cout << " Item hit ratio: -" << std::endl; // 需要实现命中计数器
}
// LFU Cache Implementation
LFUCache::LFUCache(size_t max_size) : max_size_(max_size), min_frequency_(0) {
}
LFUCache::~LFUCache() {
clear();
}
bool LFUCache::contains(const std::string& key) const {
std::lock_guard<std::mutex> lock(mutex_);
return cache_.find(key) != cache_.end();
}
bool LFUCache::put(const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(mutex_);
// Check if key already exists
auto it = cache_.find(key);
if (it != cache_.end()) {
// Update value and frequency
it->second.value = value;
// Increment frequency and update position
size_t old_freq = it->second.frequency;
size_t new_freq = old_freq + 1;
// Remove from old frequency list
freq_map_[old_freq].erase(it->second.usage_iter);
if (freq_map_[old_freq].empty() && old_freq == min_frequency_) {
min_frequency_++;
}
// Add to new frequency list
freq_map_[new_freq].push_front(key);
it->second.frequency = new_freq;
it->second.usage_iter = freq_map_[new_freq].begin();
return true;
}
// Check if cache is full
if (cache_.size() >= max_size_) {
// Remove least frequently used item
std::string lfu_key = freq_map_[min_frequency_].back();
freq_map_[min_frequency_].pop_back();
cache_.erase(lfu_key);
}
// Add new item
min_frequency_ = 1;
freq_map_[1].push_front(key);
cache_[key] = {value, 1, freq_map_[1].begin()};
return true;
}
std::string LFUCache::get(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it == cache_.end()) {
return "";
}
// Increment frequency
size_t old_freq = it->second.frequency;
size_t new_freq = old_freq + 1;
// Remove from old frequency list
freq_map_[old_freq].erase(it->second.usage_iter);
if (freq_map_[old_freq].empty() && old_freq == min_frequency_) {
min_frequency_++;
}
// Add to new frequency list
freq_map_[new_freq].push_front(key);
it->second.frequency = new_freq;
it->second.usage_iter = freq_map_[new_freq].begin();
return it->second.value;
}
bool LFUCache::remove(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it == cache_.end()) {
return false;
}
// Remove from frequency list
size_t freq = it->second.frequency;
freq_map_[freq].erase(it->second.usage_iter);
if (freq_map_[freq].empty() && freq == min_frequency_) {
min_frequency_++;
}
// Remove from cache
cache_.erase(it);
return true;
}
void LFUCache::clear() {
std::lock_guard<std::mutex> lock(mutex_);
cache_.clear();
freq_map_.clear();
min_frequency_ = 0;
}
size_t LFUCache::size() const {
std::lock_guard<std::mutex> lock(mutex_);
return cache_.size();
}
size_t LFUCache::max_size() const {
return max_size_;
}
void LFUCache::set_max_size(size_t max_size) {
std::lock_guard<std::mutex> lock(mutex_);
max_size_ = max_size;
// Evict items if necessary
while (cache_.size() > max_size_) {
std::string lfu_key = freq_map_[min_frequency_].back();
freq_map_[min_frequency_].pop_back();
cache_.erase(lfu_key);
}
}
void LFUCache::dump_cache_stats() const {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "LFU Cache Statistics:" << std::endl;
std::cout << " Current size: " << cache_.size() << std::endl;
std::cout << " Maximum size: " << max_size_ << std::endl;
std::cout << " Minimum frequency: " << min_frequency_ << std::endl;
std::cout << " Frequency distribution:" << std::endl;
for (const auto& freq_entry : freq_map_) {
if (!freq_entry.second.empty()) {
std::cout << " Frequency " << freq_entry.first << ": " << freq_entry.second.size() << " items" << std::endl;
}
}
}
// BuildCache Implementation
// 使用C++标准库实现的哈希计算函数
std::string calculate_string_hash(const std::string& input) {
std::hash<std::string> hasher;
size_t hash_val = hasher(input);
std::stringstream ss;
ss << std::hex << hash_val;
return ss.str();
}
BuildCache::BuildCache(const std::string& cache_dir, size_t max_size) : cache_dir_(cache_dir) {
// Create cache directory if it doesn't exist
std::filesystem::create_directories(cache_dir_);
// Default to LRU cache
cache_ = std::make_unique<LRUCache>(max_size);
}
BuildCache::~BuildCache() {
}
void BuildCache::set_cache_strategy(const std::string& strategy) {
std::lock_guard<std::mutex> lock(mutex_);
size_t current_size = cache_->size();
size_t max_size = cache_->max_size();
if (strategy == "lfu") {
cache_ = std::make_unique<LFUCache>(max_size);
} else {
// Default to LRU
cache_ = std::make_unique<LRUCache>(max_size);
}
}
std::string BuildCache::calculate_file_hash(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
if (!file) {
return "";
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return calculate_string_hash(content);
}
std::string BuildCache::calculate_command_hash(const std::string& command) {
return calculate_string_hash(command);
}
bool BuildCache::has_file_changed(const std::string& file_path, const std::string& last_hash) {
std::string current_hash = calculate_file_hash(file_path);
return current_hash != last_hash;
}
bool BuildCache::cache_build_result(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies,
const std::string& result) {
std::lock_guard<std::mutex> lock(mutex_);
// Create cache key: target + command + dependencies hashes
std::stringstream key_stream;
key_stream << "target=" << target << ";";
key_stream << "command=" << calculate_command_hash(command) << ";";
for (const auto& dep : dependencies) {
key_stream << "dep=" << dep << ":" << calculate_file_hash(dep) << ";";
}
std::string cache_key = calculate_string_hash(key_stream.str());
// Cache the result
return cache_->put(cache_key, result);
}
std::string BuildCache::get_cached_build_result(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies) {
std::lock_guard<std::mutex> lock(mutex_);
// Create cache key (same as in cache_build_result)
std::stringstream key_stream;
key_stream << "target=" << target << ";";
key_stream << "command=" << calculate_command_hash(command) << ";";
for (const auto& dep : dependencies) {
key_stream << "dep=" << dep << ":" << calculate_file_hash(dep) << ";";
}
std::string cache_key = calculate_string_hash(key_stream.str());
// Get cached result
return cache_->get(cache_key);
}
bool BuildCache::needs_rebuild(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies) {
std::string cached_result = get_cached_build_result(target, command, dependencies);
return cached_result.empty();
}
void BuildCache::clean_expired_cache(std::chrono::duration<int> max_age) {
// For simplicity, we're not implementing this yet
// In a real implementation, we would track item creation times and remove old items
std::cout << "Cleaning expired cache not implemented yet." << std::endl;
}
void BuildCache::clean_target_cache(const std::string& target) {
// For simplicity, we're not implementing this yet
// In a real implementation, we would find all cache entries related to the target and remove them
std::cout << "Cleaning target cache not implemented yet." << std::endl;
}
void BuildCache::clean_all_cache() {
std::lock_guard<std::mutex> lock(mutex_);
cache_->clear();
// Also clean the cache directory
if (std::filesystem::exists(cache_dir_)) {
std::filesystem::remove_all(cache_dir_);
std::filesystem::create_directories(cache_dir_);
}
}
void BuildCache::dump_build_cache_stats() const {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "Build Cache Statistics:" << std::endl;
std::cout << " Cache directory: " << cache_dir_ << std::endl;
cache_->dump_cache_stats();
}

153
cpp_cache/smart_cache.h Normal file
View File

@@ -0,0 +1,153 @@
// sikuwa/cpp_cache/smart_cache.h
// 智能缓存策略和构建缓存系统
// 仅使用C++标准库和Python C API
#ifndef SMART_CACHE_H
#define SMART_CACHE_H
#include <iostream>
#include <unordered_map>
#include <list>
#include <string>
#include <chrono>
#include <vector>
#include <memory>
#include <mutex>
#include <functional>
// 缓存项的元数据
struct CacheItemMetadata {
std::chrono::time_point<std::chrono::system_clock> created_at;
std::chrono::time_point<std::chrono::system_clock> last_accessed;
size_t size_in_bytes;
int access_count;
std::vector<std::string> dependencies;
};
// 缓存项
template<typename T>
struct CacheItem {
T value;
CacheItemMetadata metadata;
};
// 基础缓存接口
class BaseCache {
public:
virtual ~BaseCache() = default;
virtual bool contains(const std::string& key) const = 0;
virtual bool put(const std::string& key, const std::string& value) = 0;
virtual std::string get(const std::string& key) = 0;
virtual bool remove(const std::string& key) = 0;
virtual void clear() = 0;
virtual size_t size() const = 0;
virtual size_t max_size() const = 0;
virtual void set_max_size(size_t max_size) = 0;
virtual void dump_cache_stats() const = 0;
};
// LRU (Least Recently Used) 缓存实现
class LRUCache : public BaseCache {
private:
size_t max_size_;
std::unordered_map<std::string, std::pair<std::string, std::list<std::string>::iterator>> cache_;
std::list<std::string> usage_order_;
mutable std::mutex mutex_;
public:
explicit LRUCache(size_t max_size = 1000);
~LRUCache() override;
bool contains(const std::string& key) const override;
bool put(const std::string& key, const std::string& value) override;
std::string get(const std::string& key) override;
bool remove(const std::string& key) override;
void clear() override;
size_t size() const override;
size_t max_size() const override;
void set_max_size(size_t max_size) override;
void dump_cache_stats() const override;
};
// LFU (Least Frequently Used) 缓存实现
class LFUCache : public BaseCache {
private:
size_t max_size_;
struct Node {
std::string value;
size_t frequency;
std::list<std::string>::iterator usage_iter;
};
std::unordered_map<std::string, Node> cache_;
std::unordered_map<size_t, std::list<std::string>> freq_map_;
size_t min_frequency_;
mutable std::mutex mutex_;
public:
explicit LFUCache(size_t max_size = 1000);
~LFUCache() override;
bool contains(const std::string& key) const override;
bool put(const std::string& key, const std::string& value) override;
std::string get(const std::string& key) override;
bool remove(const std::string& key) override;
void clear() override;
size_t size() const override;
size_t max_size() const override;
void set_max_size(size_t max_size) override;
void dump_cache_stats() const override;
};
// 构建缓存系统
class BuildCache {
private:
std::unique_ptr<BaseCache> cache_;
std::string cache_dir_;
mutable std::mutex mutex_;
// 计算文件的哈希值
std::string calculate_file_hash(const std::string& file_path);
// 计算构建命令的哈希值
std::string calculate_command_hash(const std::string& command);
// 检查文件是否已更改
bool has_file_changed(const std::string& file_path, const std::string& last_hash);
public:
BuildCache(const std::string& cache_dir = ".cache", size_t max_size = 1000000000);
~BuildCache();
// 设置缓存策略 ("lru" 或 "lfu")
void set_cache_strategy(const std::string& strategy);
// 缓存构建结果
bool cache_build_result(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies,
const std::string& result);
// 获取缓存的构建结果
std::string get_cached_build_result(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies);
// 检查是否需要重新构建
bool needs_rebuild(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies);
// 清理过期缓存
void clean_expired_cache(std::chrono::duration<int> max_age);
// 清理特定目标的缓存
void clean_target_cache(const std::string& target);
// 清理所有缓存
void clean_all_cache();
// 导出缓存统计信息
void dump_build_cache_stats() const;
};
#endif // SMART_CACHE_H

View File

@@ -0,0 +1,69 @@
// smart_cache_minimal.cpp
// Minimal implementation of smart cache system
#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include <ctime>
#include <cstdlib>
#include "smart_cache_minimal.h"
// LRUCache implementation
LRUCache::LRUCache(size_t max_size) : max_size_(max_size) {}
bool LRUCache::put(const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(mutex_);
cache_[key] = value;
return true;
}
std::string LRUCache::get(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it == cache_.end()) {
return "";
}
return it->second;
}
bool LRUCache::contains(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
return cache_.find(key) != cache_.end();
}
void LRUCache::clear() {
std::lock_guard<std::mutex> lock(mutex_);
cache_.clear();
}
// Helper function to create directories
bool create_directory_if_not_exists(const std::string& path) {
std::string cmd = "mkdir " + path;
int result = system(cmd.c_str());
return result == 0;
}
// BuildCache implementation
BuildCache::BuildCache(const std::string& cache_dir) : cache_(10000), cache_dir_(cache_dir) {
create_directory_if_not_exists(cache_dir_);
}
bool BuildCache::cache_result(const std::string& target, const std::string& command, const std::string& result) {
std::lock_guard<std::mutex> lock(mutex_);
std::string key = target + "|" + command;
cache_.put(key, result);
return true;
}
std::string BuildCache::get_result(const std::string& target, const std::string& command) {
std::lock_guard<std::mutex> lock(mutex_);
std::string key = target + "|" + command;
return cache_.get(key);
}
bool BuildCache::needs_rebuild(const std::string& target, const std::string& command) {
std::lock_guard<std::mutex> lock(mutex_);
std::string key = target + "|" + command;
return !cache_.contains(key);
}

View File

@@ -0,0 +1,46 @@
// sikuwa/cpp_cache/smart_cache_minimal.h
// 最小化版本智能缓存系统
#ifndef SMART_CACHE_MINIMAL_H
#define SMART_CACHE_MINIMAL_H
#include <iostream>
#include <unordered_map>
#include <string>
#include <vector>
#include <mutex>
// 简单的LRU缓存实现
class LRUCache {
private:
size_t max_size_;
std::unordered_map<std::string, std::string> cache_;
std::mutex mutex_;
public:
LRUCache(size_t max_size = 1000);
bool put(const std::string& key, const std::string& value);
std::string get(const std::string& key);
bool contains(const std::string& key);
void clear();
};
// 简单的构建缓存系统
class BuildCache {
private:
LRUCache cache_;
std::string cache_dir_;
std::mutex mutex_;
public:
BuildCache(const std::string& cache_dir = ".cache");
bool cache_result(const std::string& target,
const std::string& command,
const std::string& result);
std::string get_result(const std::string& target,
const std::string& command);
bool needs_rebuild(const std::string& target,
const std::string& command);
};
#endif // SMART_CACHE_MINIMAL_H

View File

@@ -0,0 +1,214 @@
// sikuwa/cpp_cache/smart_cache_simple.cpp
// 简化版智能缓存系统实现
#include "smart_cache_simple.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstddef>
#include <filesystem>
#include <functional>
// LRU Cache Implementation
LRUCache::LRUCache(size_t max_size) : max_size_(max_size) {
}
LRUCache::~LRUCache() {
clear();
}
bool LRUCache::contains(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
return cache_.find(key) != cache_.end();
}
bool LRUCache::put(const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(mutex_);
// Check if key already exists
auto it = cache_.find(key);
if (it != cache_.end()) {
// Update value and move to front
it->second.first = value;
usage_order_.erase(it->second.second);
usage_order_.push_front(key);
it->second.second = usage_order_.begin();
return true;
}
// Check if cache is full
if (cache_.size() >= max_size_) {
// Remove least recently used item
std::string lru_key = usage_order_.back();
usage_order_.pop_back();
cache_.erase(lru_key);
}
// Add new item
usage_order_.push_front(key);
cache_[key] = std::make_pair(value, usage_order_.begin());
return true;
}
std::string LRUCache::get(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it == cache_.end()) {
return "";
}
// Move accessed key to front
usage_order_.erase(it->second.second);
usage_order_.push_front(key);
it->second.second = usage_order_.begin();
return it->second.first;
}
bool LRUCache::remove(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it == cache_.end()) {
return false;
}
usage_order_.erase(it->second.second);
cache_.erase(it);
return true;
}
void LRUCache::clear() {
std::lock_guard<std::mutex> lock(mutex_);
cache_.clear();
usage_order_.clear();
}
size_t LRUCache::size() {
std::lock_guard<std::mutex> lock(mutex_);
return cache_.size();
}
size_t LRUCache::max_size() {
return max_size_;
}
void LRUCache::set_max_size(size_t max_size) {
std::lock_guard<std::mutex> lock(mutex_);
max_size_ = max_size;
// Evict items if necessary
while (cache_.size() > max_size_) {
std::string lru_key = usage_order_.back();
usage_order_.pop_back();
cache_.erase(lru_key);
}
}
void LRUCache::dump_cache_stats() {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "LRU Cache Statistics:" << std::endl;
std::cout << " Current size: " << cache_.size() << std::endl;
std::cout << " Maximum size: " << max_size_ << std::endl;
}
// BuildCache Implementation
// 使用简单的哈希计算函数
std::string BuildCache::calculate_hash(const std::string& input) {
std::hash<std::string> hasher;
size_t hash_val = hasher(input);
std::stringstream ss;
ss << std::hex << hash_val;
return ss.str();
}
std::string BuildCache::calculate_file_hash(const std::string& file_path) {
std::ifstream file(file_path, std::ios::binary);
if (!file) {
return "";
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return calculate_hash(content);
}
BuildCache::BuildCache(const std::string& cache_dir, size_t max_size) : cache_dir_(cache_dir) {
// Create cache directory if it doesn't exist
std::filesystem::create_directories(cache_dir_);
// Use LRU cache
cache_ = std::make_unique<LRUCache>(max_size);
}
BuildCache::~BuildCache() {
}
bool BuildCache::cache_build_result(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies,
const std::string& result) {
std::lock_guard<std::mutex> lock(mutex_);
// Create cache key: target + command + dependencies hashes
std::stringstream key_stream;
key_stream << "target=" << target << ";";
key_stream << "command=" << calculate_hash(command) << ";";
for (const auto& dep : dependencies) {
key_stream << "dep=" << dep << ":" << calculate_file_hash(dep) << ";";
}
std::string cache_key = calculate_hash(key_stream.str());
// Cache the result
return cache_->put(cache_key, result);
}
std::string BuildCache::get_cached_build_result(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies) {
std::lock_guard<std::mutex> lock(mutex_);
// Create cache key (same as in cache_build_result)
std::stringstream key_stream;
key_stream << "target=" << target << ";";
key_stream << "command=" << calculate_hash(command) << ";";
for (const auto& dep : dependencies) {
key_stream << "dep=" << dep << ":" << calculate_file_hash(dep) << ";";
}
std::string cache_key = calculate_hash(key_stream.str());
// Get cached result
return cache_->get(cache_key);
}
bool BuildCache::needs_rebuild(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies) {
std::string cached_result = get_cached_build_result(target, command, dependencies);
return cached_result.empty();
}
void BuildCache::clean_all_cache() {
std::lock_guard<std::mutex> lock(mutex_);
cache_->clear();
// Also clean the cache directory
if (std::filesystem::exists(cache_dir_)) {
std::filesystem::remove_all(cache_dir_);
std::filesystem::create_directories(cache_dir_);
}
}
void BuildCache::dump_build_cache_stats() {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "Build Cache Statistics:" << std::endl;
std::cout << " Cache directory: " << cache_dir_ << std::endl;
cache_->dump_cache_stats();
}

View File

@@ -0,0 +1,77 @@
// sikuwa/cpp_cache/smart_cache_simple.h
// 简化版智能缓存系统
#ifndef SMART_CACHE_SIMPLE_H
#define SMART_CACHE_SIMPLE_H
#include <iostream>
#include <unordered_map>
#include <list>
#include <string>
#include <vector>
#include <memory>
#include <mutex>
// LRU (Least Recently Used) 缓存实现
class LRUCache {
private:
size_t max_size_;
std::unordered_map<std::string, std::pair<std::string, std::list<std::string>::iterator>> cache_;
std::list<std::string> usage_order_;
std::mutex mutex_;
public:
LRUCache(size_t max_size = 1000);
~LRUCache();
bool contains(const std::string& key);
bool put(const std::string& key, const std::string& value);
std::string get(const std::string& key);
bool remove(const std::string& key);
void clear();
size_t size();
size_t max_size();
void set_max_size(size_t max_size);
void dump_cache_stats();
};
// 构建缓存系统
class BuildCache {
private:
std::unique_ptr<LRUCache> cache_;
std::string cache_dir_;
std::mutex mutex_;
// 计算字符串的哈希值
std::string calculate_hash(const std::string& input);
// 计算文件的哈希值
std::string calculate_file_hash(const std::string& file_path);
public:
BuildCache(const std::string& cache_dir = ".cache", size_t max_size = 1000000000);
~BuildCache();
// 缓存构建结果
bool cache_build_result(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies,
const std::string& result);
// 获取缓存的构建结果
std::string get_cached_build_result(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies);
// 检查是否需要重新构建
bool needs_rebuild(const std::string& target,
const std::string& command,
const std::vector<std::string>& dependencies);
// 清理所有缓存
void clean_all_cache();
// 导出缓存统计信息
void dump_build_cache_stats();
};
#endif // SMART_CACHE_SIMPLE_H

77
i18n.py Normal file
View File

@@ -0,0 +1,77 @@
# sikuwa/i18n.py
"""
国际化(i18n)支持模块
"""
import os
import gettext
from pathlib import Path
from typing import Optional
# 支持的语言列表
supported_languages = ['zh_CN', 'en_US']
# 默认语言
default_language = 'zh_CN'
# 翻译目录
LOCALES_DIR = Path(__file__).parent / 'i18n' / 'locales'
# 当前翻译对象
trans = None
def setup_i18n(force_lang: Optional[str] = None):
"""
初始化国际化支持(添加调试功能)
Args:
force_lang: 强制使用的语言,如 'en_US''zh_CN',用于调试
"""
global trans
# 验证语言是否支持
if force_lang not in supported_languages:
force_lang = default_language
# 设置环境变量
os.environ['LANGUAGE'] = force_lang
# 创建翻译对象
trans = gettext.translation(
domain='sikuwa',
localedir=LOCALES_DIR,
languages=[force_lang],
fallback=True
)
# 安装翻译函数
trans.install()
# 返回选择的语言,便于调试时确认当前使用的语言
return force_lang
# 初始化翻译(默认使用系统语言)
selected_lang = setup_i18n()
# 调试:打印当前使用的语言
print(f"[i18n调试] 当前使用的语言: {selected_lang}")
# 导出_函数供其他模块使用
_ = trans.gettext
def test_translation():
"""
测试国际化是否正常工作的调试函数
"""
print("[i18n调试] 开始测试翻译...")
test_strings = [
_("Hello, world!"),
_("Welcome to Sikuwa"),
_("Settings"),
_("Exit")
]
for idx, s in enumerate(test_strings, 1):
print(f"[i18n调试] 翻译测试 {idx}: {s}")
print("[i18n调试] 翻译测试结束")

34
i18n/__init__.py Normal file
View File

@@ -0,0 +1,34 @@
import gettext
import os
from pathlib import Path
# 获取翻译文件目录
i18n_dir = Path(__file__).parent
locale_dir = i18n_dir / "locales"
# 初始化翻译系统
translation = gettext.translation(
"sikuwa",
localedir=str(locale_dir), # 使用字符串路径
languages=None, # 使用系统默认语言
fallback=True # 如果找不到翻译文件,使用原始字符串
)
# 导出翻译函数
_ = translation.gettext
# 提供切换语言的功能
def set_language(lang_code):
"""切换当前使用的语言"""
global translation, _
try:
translation = gettext.translation(
"sikuwa",
localedir=str(locale_dir), # 使用字符串路径
languages=[lang_code],
fallback=True
)
_ = translation.gettext
return True
except Exception as e:
return False

Binary file not shown.

View File

@@ -0,0 +1,210 @@
# English translations for Sikuwa package.
# Copyright (C) 2025 Sikuwa Team
# This file is distributed under the same license as the Sikuwa package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: Sikuwa 1.2.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-20 15:00+0800\n"
"PO-Revision-Date: 2025-11-20 15:30+0800\n"
"Last-Translator: Sikuwa Team <team@sikuwa.dev>\n"
"Language-Team: English <en@li.org>\n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
# CLI messages
msgid "详细输出模式"
msgstr "Verbose output mode"
msgid "强制重新构建"
msgstr "Force rebuild"
msgid "覆盖已存在的配置文件"
msgstr "Overwrite existing configuration file"
msgid "有序集合支持"
msgstr "Ordered set support"
msgid "诊断完成"
msgstr "Diagnosis completed"
msgid "完整配置"
msgstr "Full configuration"
# Log messages
msgid "带颜色的日志格式化器"
msgstr "Colored log formatter"
msgid "注册所有自定义日志级别"
msgstr "Register all custom log levels"
msgid "极细粒度状态变更"
msgstr "Ultra-fine-grained state changes"
msgid "微观性能计时"
msgstr "Micro performance timing"
msgid "函数进入退出跟踪"
msgstr "Function entry/exit tracking"
msgid "详细调试信息"
msgstr "Detailed debugging information"
msgid "业务操作记录"
msgstr "Business operation records"
msgid "用户可见操作"
msgstr "User-visible operations"
msgid "周期性指标快照"
msgstr "Periodic metrics snapshots"
msgid "健康检查通过"
msgstr "Health check passed"
msgid "非致命配置变更"
msgstr "Non-fatal configuration changes"
msgid "接近阈值"
msgstr "Approaching threshold"
msgid "轻微异常"
msgstr "Minor anomalies"
msgid "重试事件"
msgstr "Retry events"
msgid "资源接近上限"
msgstr "Resources approaching limit"
msgid "使用不推荐接口"
msgstr "Using deprecated interface"
msgid "可疑安全事件"
msgstr "Suspicious security events"
msgid "业务错误"
msgstr "Business errors"
msgid "数据库错误"
msgstr "Database errors"
msgid "数据一致性问题"
msgstr "Data consistency issues"
msgid "外部依赖失败"
msgstr "External dependency failures"
msgid "已确认安全问题"
msgstr "Confirmed security issues"
msgid "服务功能不可用"
msgstr "Service function unavailable"
msgid "数据持久化失败"
msgstr "Data persistence failed"
msgid "系统降级"
msgstr "System degradation"
msgid "级联故障"
msgstr "Cascading failures"
msgid "严重安全事件"
msgstr "Severe security events"
msgid "获取全局日志器实例"
msgstr "Get global logger instance"
msgid "设置日志级别"
msgstr "Set log level"
msgid "测试计时块"
msgstr "Test timing block"
# Builder messages
msgid "设置构建目录"
msgstr "Set up build directories"
msgid "输出目录"
msgstr "Output directory"
msgid "构建目录"
msgstr "Build directory"
msgid "日志目录"
msgstr "Log directory"
msgid "执行完整构建流程"
msgstr "Execute complete build process"
msgid "完整构建流程"
msgstr "Complete build process"
msgid "验证环境"
msgstr "Verify environment"
msgid "准备源代码"
msgstr "Prepare source code"
msgid "编译所有平台"
msgstr "Compile for all platforms"
msgid "复制资源"
msgstr "Copy resources"
msgid "生成清单"
msgstr "Generate manifest"
msgid "验证构建环境"
msgstr "Verify build environment"
msgid "构建所有平台"
msgstr "Build all platforms"
msgid "构建单一平台"
msgstr "Build single platform"
msgid "复制资源文件"
msgstr "Copy resource files"
msgid "没有需要复制的资源文件"
msgstr "No resource files need to be copied"
msgid "生成构建清单"
msgstr "Generate build manifest"
msgid "清理构建文件"
msgstr "Clean up build files"
msgid "构建器工厂"
msgstr "Builder factory"
msgid "从配置创建构建器"
msgstr "Create builder from configuration"
msgid "从配置文件创建构建器"
msgstr "Create builder from configuration file"
msgid "构建项目"
msgstr "Build project"
msgid "启动构建流程"
msgstr "Start build process"
msgid "构建流程完成"
msgstr "Build process completed"
msgid "清理项目"
msgstr "Clean up project"
msgid "启动清理流程"
msgstr "Start cleanup process"
msgid "清理流程完成"
msgstr "Cleanup process completed"

211
i18n/sikuwa.pot Normal file
View File

@@ -0,0 +1,211 @@
# Sikuwa translation template
# Copyright (C) 2025 Sikuwa Team
# This file is distributed under the same license as the Sikuwa package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Sikuwa 1.2.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-20 15:00+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
# CLI messages
msgid "详细输出模式"
msgstr ""
msgid "强制重新构建"
msgstr ""
msgid "覆盖已存在的配置文件"
msgstr ""
msgid "有序集合支持"
msgstr ""
msgid "诊断完成"
msgstr ""
msgid "完整配置"
msgstr ""
# Log messages
msgid "带颜色的日志格式化器"
msgstr ""
msgid "注册所有自定义日志级别"
msgstr ""
msgid "极细粒度状态变更"
msgstr ""
msgid "微观性能计时"
msgstr ""
msgid "函数进入退出跟踪"
msgstr ""
msgid "详细调试信息"
msgstr ""
msgid "业务操作记录"
msgstr ""
msgid "用户可见操作"
msgstr ""
msgid "周期性指标快照"
msgstr ""
msgid "健康检查通过"
msgstr ""
msgid "非致命配置变更"
msgstr ""
msgid "接近阈值"
msgstr ""
msgid "轻微异常"
msgstr ""
msgid "重试事件"
msgstr ""
msgid "资源接近上限"
msgstr ""
msgid "使用不推荐接口"
msgstr ""
msgid "可疑安全事件"
msgstr ""
msgid "业务错误"
msgstr ""
msgid "数据库错误"
msgstr ""
msgid "数据一致性问题"
msgstr ""
msgid "外部依赖失败"
msgstr ""
msgid "已确认安全问题"
msgstr ""
msgid "服务功能不可用"
msgstr ""
msgid "数据持久化失败"
msgstr ""
msgid "系统降级"
msgstr ""
msgid "级联故障"
msgstr ""
msgid "严重安全事件"
msgstr ""
msgid "获取全局日志器实例"
msgstr ""
msgid "设置日志级别"
msgstr ""
msgid "测试计时块"
msgstr ""
# Builder messages
msgid "设置构建目录"
msgstr ""
msgid "输出目录"
msgstr ""
msgid "构建目录"
msgstr ""
msgid "日志目录"
msgstr ""
msgid "执行完整构建流程"
msgstr ""
msgid "完整构建流程"
msgstr ""
msgid "验证环境"
msgstr ""
msgid "准备源代码"
msgstr ""
msgid "编译所有平台"
msgstr ""
msgid "复制资源"
msgstr ""
msgid "生成清单"
msgstr ""
msgid "验证构建环境"
msgstr ""
msgid "构建所有平台"
msgstr ""
msgid "构建单一平台"
msgstr ""
msgid "复制资源文件"
msgstr ""
msgid "没有需要复制的资源文件"
msgstr ""
msgid "生成构建清单"
msgstr ""
msgid "清理构建文件"
msgstr ""
msgid "构建器工厂"
msgstr ""
msgid "从配置创建构建器"
msgstr ""
msgid "从配置文件创建构建器"
msgstr ""
msgid "构建项目"
msgstr ""
msgid "启动构建流程"
msgstr ""
msgid "构建流程完成"
msgstr ""
msgid "清理项目"
msgstr ""
msgid "启动清理流程"
msgstr ""
msgid "清理流程完成"
msgstr ""

84
incremental/__init__.py Normal file
View File

@@ -0,0 +1,84 @@
# sikuwa/incremental/__init__.py
"""
减量编译模块 - Incremental Compilation System
指哪编哪,精准编译
核心功能:
1. 单行/最小语法块为最小编译单元
2. 每个单元有唯一标识、最小依赖集、缓存产物
3. 版本快照对比检测变更
4. 只编译变更单元及受依赖影响的关联单元
5. 边界触发器处理函数/类
6. 按原始顺序拼接产物
智能缓存 V1.2
- 编译即缓存:每次编译自动记录,全历史可追溯
- 缓存即编译:缓存命中等同于零成本编译
- 预测缓存预热:基于访问模式预测并预编译
"""
from .core import (
IncrementalCompiler,
CompilationUnit,
Snapshot,
ChangeRecord,
ChangeDetector,
CompilationCache,
UnitType,
UnitState,
)
from .analyzer import (
PythonAnalyzer,
CodeBlock,
BlockType,
)
from .compiler_integration import (
IncrementalNativeCompiler,
IncrementalBuildResult,
create_incremental_native_compiler,
)
from .smart_cache import (
SmartCache,
CacheEntry,
CacheEvent,
CacheEventType,
get_smart_cache,
create_smart_cache,
)
__all__ = [
# 核心类
'IncrementalCompiler',
'CompilationUnit',
'Snapshot',
'ChangeRecord',
'ChangeDetector',
'CompilationCache',
# 枚举
'UnitType',
'UnitState',
# 分析器
'PythonAnalyzer',
'CodeBlock',
'BlockType',
# 集成编译器
'IncrementalNativeCompiler',
'IncrementalBuildResult',
'create_incremental_native_compiler',
# 智能缓存 V1.2
'SmartCache',
'CacheEntry',
'CacheEvent',
'CacheEventType',
'get_smart_cache',
'create_smart_cache',
]
__version__ = '1.2.0'

396
incremental/analyzer.py Normal file
View File

@@ -0,0 +1,396 @@
# sikuwa/incremental/analyzer.py
"""
Python 代码分析器 - 识别代码块边界和依赖关系
用于减量编译的 AST 分析
"""
import ast
import hashlib
from enum import Enum, auto
from dataclasses import dataclass, field
from typing import List, Dict, Set, Optional, Tuple
from pathlib import Path
class BlockType(Enum):
"""代码块类型"""
MODULE = auto() # 模块级
IMPORT = auto() # 导入语句
CLASS = auto() # 类定义
FUNCTION = auto() # 函数定义
METHOD = auto() # 方法定义
DECORATOR = auto() # 装饰器
STATEMENT = auto() # 普通语句
ASSIGNMENT = auto() # 赋值语句
EXPRESSION = auto() # 表达式
CONTROL = auto() # 控制流 (if/for/while/try)
WITH = auto() # with 语句
@dataclass
class CodeBlock:
"""代码块 - 最小编译单元"""
id: str = "" # 唯一标识
type: BlockType = BlockType.STATEMENT
name: str = "" # 名称(函数名/类名等)
start_line: int = 0 # 起始行 (1-based)
end_line: int = 0 # 结束行 (1-based)
start_col: int = 0 # 起始列
end_col: int = 0 # 结束列
content: str = "" # 源代码内容
content_hash: str = "" # 内容哈希
parent_id: str = "" # 父块ID
children: List[str] = field(default_factory=list) # 子块ID列表
# 依赖信息
imports: List[str] = field(default_factory=list) # 导入的模块/名称
references: List[str] = field(default_factory=list) # 引用的名称
definitions: List[str] = field(default_factory=list) # 定义的名称
dependencies: List[str] = field(default_factory=list) # 依赖的块ID
def compute_hash(self) -> str:
"""计算内容哈希"""
# 去除空白差异的影响
normalized = '\n'.join(line.strip() for line in self.content.splitlines())
self.content_hash = hashlib.sha256(normalized.encode()).hexdigest()[:16]
return self.content_hash
def generate_id(self, file_path: str) -> str:
"""生成唯一ID"""
if not self.content_hash:
self.compute_hash()
self.id = f"{file_path}:{self.start_line}:{self.end_line}:{self.content_hash[:8]}"
return self.id
class PythonAnalyzer:
"""
Python 代码分析器
分析代码结构,识别编译单元边界和依赖关系
"""
def __init__(self):
self.blocks: List[CodeBlock] = []
self.block_map: Dict[str, CodeBlock] = {}
self.lines: List[str] = []
self.file_path: str = ""
def analyze(self, source: str, file_path: str = "<string>") -> List[CodeBlock]:
"""
分析 Python 源代码,返回代码块列表
Args:
source: Python 源代码
file_path: 文件路径
Returns:
代码块列表
"""
self.file_path = file_path
self.lines = source.splitlines()
self.blocks = []
self.block_map = {}
try:
tree = ast.parse(source)
self._analyze_module(tree, source)
except SyntaxError as e:
# 语法错误时回退到行级分析
self._fallback_line_analysis(source)
# 分析依赖关系
self._analyze_dependencies()
return self.blocks
def _analyze_module(self, tree: ast.Module, source: str):
"""分析模块级 AST"""
for node in ast.iter_child_nodes(tree):
block = self._node_to_block(node, source)
if block:
self.blocks.append(block)
self.block_map[block.id] = block
def _node_to_block(self, node: ast.AST, source: str, parent_id: str = "") -> Optional[CodeBlock]:
"""将 AST 节点转换为代码块"""
block = CodeBlock()
block.parent_id = parent_id
# 获取行号范围
block.start_line = getattr(node, 'lineno', 0)
block.end_line = getattr(node, 'end_lineno', block.start_line)
block.start_col = getattr(node, 'col_offset', 0)
block.end_col = getattr(node, 'end_col_offset', 0)
# 提取源代码内容
if block.start_line > 0 and block.end_line > 0:
block.content = self._get_source_lines(block.start_line, block.end_line)
# 根据节点类型设置块类型和名称
if isinstance(node, ast.Import):
block.type = BlockType.IMPORT
block.name = "import"
block.imports = [alias.name for alias in node.names]
elif isinstance(node, ast.ImportFrom):
block.type = BlockType.IMPORT
block.name = f"from {node.module}"
block.imports = [node.module or ""] + [alias.name for alias in node.names]
elif isinstance(node, ast.ClassDef):
block.type = BlockType.CLASS
block.name = node.name
block.definitions = [node.name]
# 处理装饰器
if node.decorator_list:
block.start_line = node.decorator_list[0].lineno
# 递归处理类体
for child in node.body:
child_block = self._node_to_block(child, source, block.id)
if child_block:
block.children.append(child_block.id)
self.blocks.append(child_block)
self.block_map[child_block.id] = child_block
elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
block.type = BlockType.FUNCTION if not parent_id else BlockType.METHOD
block.name = node.name
block.definitions = [node.name]
# 处理装饰器
if node.decorator_list:
block.start_line = node.decorator_list[0].lineno
# 分析函数体中的引用
block.references = self._extract_references(node)
elif isinstance(node, ast.Assign):
block.type = BlockType.ASSIGNMENT
block.definitions = self._extract_targets(node.targets)
block.references = self._extract_references(node.value)
elif isinstance(node, ast.AugAssign):
block.type = BlockType.ASSIGNMENT
block.definitions = self._extract_targets([node.target])
block.references = self._extract_references(node.value)
elif isinstance(node, ast.AnnAssign):
block.type = BlockType.ASSIGNMENT
if node.target:
block.definitions = self._extract_targets([node.target])
if node.value:
block.references = self._extract_references(node.value)
elif isinstance(node, (ast.If, ast.For, ast.While, ast.Try)):
block.type = BlockType.CONTROL
block.name = node.__class__.__name__.lower()
block.references = self._extract_references(node)
elif isinstance(node, ast.With):
block.type = BlockType.WITH
block.references = self._extract_references(node)
elif isinstance(node, ast.Expr):
block.type = BlockType.EXPRESSION
block.references = self._extract_references(node.value)
else:
block.type = BlockType.STATEMENT
block.references = self._extract_references(node)
# 计算哈希并生成ID
block.compute_hash()
block.generate_id(self.file_path)
return block
def _get_source_lines(self, start: int, end: int) -> str:
"""获取指定行范围的源代码"""
if start < 1 or end > len(self.lines):
return ""
return '\n'.join(self.lines[start-1:end])
def _extract_references(self, node: ast.AST) -> List[str]:
"""提取节点中引用的名称"""
refs = []
for child in ast.walk(node):
if isinstance(child, ast.Name):
refs.append(child.id)
elif isinstance(child, ast.Attribute):
# 收集属性链的根名称
current = child
while isinstance(current, ast.Attribute):
current = current.value
if isinstance(current, ast.Name):
refs.append(current.id)
return list(set(refs))
def _extract_targets(self, targets: List[ast.AST]) -> List[str]:
"""提取赋值目标的名称"""
names = []
for target in targets:
if isinstance(target, ast.Name):
names.append(target.id)
elif isinstance(target, ast.Tuple) or isinstance(target, ast.List):
for elt in target.elts:
if isinstance(elt, ast.Name):
names.append(elt.id)
return names
def _analyze_dependencies(self):
"""分析块之间的依赖关系"""
# 构建名称到块的映射
name_to_block: Dict[str, str] = {}
for block in self.blocks:
for name in block.definitions:
name_to_block[name] = block.id
# 分析每个块的依赖
for block in self.blocks:
for ref in block.references:
if ref in name_to_block and name_to_block[ref] != block.id:
dep_id = name_to_block[ref]
if dep_id not in block.dependencies:
block.dependencies.append(dep_id)
def _fallback_line_analysis(self, source: str):
"""回退到行级分析(用于语法错误的代码)"""
lines = source.splitlines()
current_block = None
indent_stack = [(0, None)] # (indent, block)
for i, line in enumerate(lines, 1):
stripped = line.lstrip()
if not stripped or stripped.startswith('#'):
continue
indent = len(line) - len(stripped)
# 简单的块检测
if stripped.startswith('def ') or stripped.startswith('async def '):
block = CodeBlock(
type=BlockType.FUNCTION,
name=stripped.split('(')[0].replace('def ', '').replace('async ', '').strip(),
start_line=i,
end_line=i,
content=line
)
current_block = block
elif stripped.startswith('class '):
block = CodeBlock(
type=BlockType.CLASS,
name=stripped.split('(')[0].split(':')[0].replace('class ', '').strip(),
start_line=i,
end_line=i,
content=line
)
current_block = block
elif stripped.startswith('import ') or stripped.startswith('from '):
block = CodeBlock(
type=BlockType.IMPORT,
start_line=i,
end_line=i,
content=line
)
block.compute_hash()
block.generate_id(self.file_path)
self.blocks.append(block)
self.block_map[block.id] = block
continue
else:
if current_block and indent > indent_stack[-1][0]:
# 继续当前块
current_block.end_line = i
current_block.content += '\n' + line
else:
# 结束当前块
if current_block:
current_block.compute_hash()
current_block.generate_id(self.file_path)
self.blocks.append(current_block)
self.block_map[current_block.id] = current_block
current_block = None
# 普通语句
block = CodeBlock(
type=BlockType.STATEMENT,
start_line=i,
end_line=i,
content=line
)
block.compute_hash()
block.generate_id(self.file_path)
self.blocks.append(block)
self.block_map[block.id] = block
# 处理最后一个块
if current_block:
current_block.compute_hash()
current_block.generate_id(self.file_path)
self.blocks.append(current_block)
self.block_map[current_block.id] = current_block
def get_blocks_in_range(self, start_line: int, end_line: int) -> List[CodeBlock]:
"""获取指定行范围内的代码块"""
result = []
for block in self.blocks:
# 检查是否有交集
if block.start_line <= end_line and block.end_line >= start_line:
result.append(block)
return result
def get_affected_blocks(self, changed_block_ids: Set[str]) -> Set[str]:
"""获取受变更影响的所有块(包括依赖传播)"""
affected = set(changed_block_ids)
queue = list(changed_block_ids)
while queue:
block_id = queue.pop(0)
# 找出依赖此块的所有块
for block in self.blocks:
if block_id in block.dependencies and block.id not in affected:
affected.add(block.id)
queue.append(block.id)
return affected
def expand_to_boundaries(self, block_ids: Set[str]) -> Set[str]:
"""扩展块ID集合确保完整结构被包含"""
expanded = set(block_ids)
for block_id in list(block_ids):
block = self.block_map.get(block_id)
if not block:
continue
# 如果块在某个函数/类内,需要重新编译整个结构
if block.parent_id:
parent = self.block_map.get(block.parent_id)
if parent and parent.type in (BlockType.CLASS, BlockType.FUNCTION):
expanded.add(parent.id)
# 也包含所有子块
for child_id in parent.children:
expanded.add(child_id)
# 如果块是函数/类,包含所有子块
if block.type in (BlockType.CLASS, BlockType.FUNCTION):
for child_id in block.children:
expanded.add(child_id)
return expanded
def analyze_python_file(file_path: str) -> List[CodeBlock]:
"""分析 Python 文件"""
with open(file_path, 'r', encoding='utf-8') as f:
source = f.read()
analyzer = PythonAnalyzer()
return analyzer.analyze(source, file_path)
def analyze_python_source(source: str, file_path: str = "<string>") -> List[CodeBlock]:
"""分析 Python 源代码"""
analyzer = PythonAnalyzer()
return analyzer.analyze(source, file_path)

View File

@@ -0,0 +1,322 @@
# sikuwa/incremental/compiler_integration.py
"""
减量编译器集成模块
将减量编译系统与 Sikuwa 编译器集成
"""
import os
import sys
import subprocess
import tempfile
from pathlib import Path
from typing import Dict, List, Optional, Callable, Any
from dataclasses import dataclass
from .core import (
IncrementalCompiler,
CompilationUnit,
ChangeRecord,
UnitState,
UnitType
)
from .analyzer import PythonAnalyzer, CodeBlock, BlockType
@dataclass
class IncrementalBuildResult:
"""减量编译结果"""
success: bool = False
compiled_units: int = 0
cached_units: int = 0
total_units: int = 0
output_files: Dict[str, str] = None # unit_id -> output_path
combined_output: str = ""
errors: List[str] = None
def __post_init__(self):
if self.output_files is None:
self.output_files = {}
if self.errors is None:
self.errors = []
class IncrementalNativeCompiler:
"""
减量原生编译器
集成减量编译系统与原生 C/C++ 编译流程:
Python → C → GCC → dll/so
特点:
- 只编译变更的代码块
- 缓存已编译的目标文件
- 智能链接(只重新链接必要的部分)
"""
def __init__(self,
cache_dir: str = ".sikuwa_cache",
cc: str = "gcc",
cxx: str = "g++"):
self.incremental = IncrementalCompiler(cache_dir)
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(parents=True, exist_ok=True)
self.cc = cc
self.cxx = cxx
# 工作目录
self.work_dir = self.cache_dir / "incremental_build"
self.c_dir = self.work_dir / "c_source"
self.obj_dir = self.work_dir / "obj"
for d in [self.work_dir, self.c_dir, self.obj_dir]:
d.mkdir(parents=True, exist_ok=True)
# 设置编译回调
self.incremental.set_compiler(self._compile_unit)
# Cython 可用性
self._cython_available = self._check_cython()
def _check_cython(self) -> bool:
"""检查 Cython 是否可用"""
try:
import Cython
return True
except ImportError:
return False
def _compile_unit(self, unit: CompilationUnit) -> str:
"""
编译单个单元
流程Python 代码 → C 代码 → 目标文件
"""
# 生成 C 代码
c_code = self._python_to_c(unit)
# 保存 C 文件
c_file = self.c_dir / f"unit_{unit.content_hash}.c"
c_file.write_text(c_code, encoding='utf-8')
# 编译为目标文件
obj_file = self.obj_dir / f"unit_{unit.content_hash}.o"
if not obj_file.exists():
self._compile_c_to_obj(c_file, obj_file)
# 返回目标文件路径作为"编译产物"
return str(obj_file)
def _python_to_c(self, unit: CompilationUnit) -> str:
"""
Python 代码转 C 代码
使用 Cython 或内置转换器
"""
if self._cython_available and unit.type in (UnitType.FUNCTION, UnitType.CLASS):
return self._cython_convert(unit)
else:
return self._builtin_convert(unit)
def _cython_convert(self, unit: CompilationUnit) -> str:
"""使用 Cython 转换"""
# 创建临时 .pyx 文件
pyx_file = self.work_dir / f"temp_{unit.content_hash}.pyx"
pyx_file.write_text(unit.content, encoding='utf-8')
c_file = self.work_dir / f"temp_{unit.content_hash}.c"
try:
result = subprocess.run(
[sys.executable, "-m", "cython", "-3", str(pyx_file), "-o", str(c_file)],
capture_output=True,
text=True
)
if result.returncode == 0 and c_file.exists():
return c_file.read_text(encoding='utf-8')
except Exception:
pass
# 回退到内置转换
return self._builtin_convert(unit)
def _builtin_convert(self, unit: CompilationUnit) -> str:
"""内置转换器 - 将 Python 代码嵌入 C"""
escaped = unit.content.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
unit_name = unit.name or f"unit_{unit.content_hash[:8]}"
safe_name = ''.join(c if c.isalnum() else '_' for c in unit_name)
c_code = f'''
/* Auto-generated by Sikuwa Incremental Compiler */
/* Unit: {unit.id} */
/* Lines: {unit.start_line}-{unit.end_line} */
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static const char* sikuwa_unit_{safe_name}_source = "{escaped}";
int sikuwa_exec_unit_{safe_name}(PyObject* globals, PyObject* locals) {{
PyObject* code = Py_CompileString(
sikuwa_unit_{safe_name}_source,
"{unit.file_path}",
Py_file_input
);
if (code == NULL) {{
return -1;
}}
PyObject* result = PyEval_EvalCode(code, globals, locals);
Py_DECREF(code);
if (result == NULL) {{
return -1;
}}
Py_DECREF(result);
return 0;
}}
'''
return c_code
def _compile_c_to_obj(self, c_file: Path, obj_file: Path):
"""编译 C 文件为目标文件"""
import sysconfig
# 获取 Python 头文件路径
include_dir = sysconfig.get_path('include')
cmd = [
self.cc,
"-c",
"-fPIC",
"-O2",
f"-I{include_dir}",
str(c_file),
"-o", str(obj_file)
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"Compilation failed: {result.stderr}")
def build(self, file_path: str, content: str) -> IncrementalBuildResult:
"""
执行减量编译
Args:
file_path: 源文件路径
content: 源代码内容
Returns:
编译结果
"""
result = IncrementalBuildResult()
try:
# 检测变更
changes = self.incremental.update_source(file_path, content)
# 获取需要编译的单元
units_to_compile = self.incremental.get_units_to_compile()
result.total_units = len(self.incremental._units)
# 编译变更的单元
compiled_outputs = self.incremental.compile_all_pending()
result.compiled_units = len(compiled_outputs)
result.cached_units = result.total_units - result.compiled_units
# 收集输出
result.output_files = compiled_outputs
# 获取合并输出(所有目标文件路径)
result.combined_output = self.incremental.get_combined_output(file_path)
result.success = True
except Exception as e:
result.success = False
result.errors.append(str(e))
return result
def link(self, output_path: str, file_paths: List[str]) -> bool:
"""
链接所有目标文件
Args:
output_path: 输出文件路径
file_paths: 源文件路径列表
Returns:
是否成功
"""
import sysconfig
# 收集所有目标文件
obj_files = []
for fp in file_paths:
combined = self.incremental.get_combined_output(fp)
for line in combined.splitlines():
if line.strip() and line.endswith('.o'):
obj_files.append(line.strip())
if not obj_files:
return False
# 获取 Python 库路径
lib_dir = sysconfig.get_config_var('LIBDIR') or '/usr/lib'
# 判断输出类型
if output_path.endswith('.so') or output_path.endswith('.dll'):
link_flags = ["-shared"]
else:
link_flags = []
# 链接命令
cmd = [
self.cxx,
*link_flags,
*obj_files,
f"-L{lib_dir}",
f"-lpython{sys.version_info.major}.{sys.version_info.minor}",
"-o", output_path
]
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode == 0
def get_stats(self) -> Dict[str, Any]:
"""获取统计信息"""
stats = self.incremental.get_stats()
stats['c_files'] = len(list(self.c_dir.glob('*.c')))
stats['obj_files'] = len(list(self.obj_dir.glob('*.o')))
return stats
def clean(self):
"""清理所有缓存和临时文件"""
import shutil
self.incremental.clear()
for d in [self.c_dir, self.obj_dir]:
if d.exists():
shutil.rmtree(d)
d.mkdir(parents=True, exist_ok=True)
def save(self):
"""保存状态"""
self.incremental.save()
def create_incremental_native_compiler(
cache_dir: str = ".sikuwa_cache",
cc: str = "gcc",
cxx: str = "g++"
) -> IncrementalNativeCompiler:
"""创建减量原生编译器"""
return IncrementalNativeCompiler(cache_dir, cc, cxx)

778
incremental/core.py Normal file
View File

@@ -0,0 +1,778 @@
# sikuwa/incremental/core.py
"""
减量编译核心 - Python 实现
指哪编哪:只编译源码改变的部分
提供 C++ 扩展不可用时的纯 Python 回退实现
"""
import hashlib
import json
import os
import time
from enum import Enum, auto
from dataclasses import dataclass, field
from typing import List, Dict, Set, Optional, Tuple, Callable, Any
from pathlib import Path
from .analyzer import PythonAnalyzer, CodeBlock, BlockType
class UnitType(Enum):
"""编译单元类型"""
LINE = auto()
STATEMENT = auto()
FUNCTION = auto()
CLASS = auto()
MODULE = auto()
IMPORT = auto()
DECORATOR = auto()
BLOCK = auto()
class UnitState(Enum):
"""编译单元状态"""
UNKNOWN = auto()
UNCHANGED = auto()
MODIFIED = auto()
ADDED = auto()
DELETED = auto()
AFFECTED = auto()
@dataclass
class CompilationUnit:
"""编译单元 - 最小编译粒度"""
id: str = ""
file_path: str = ""
start_line: int = 0
end_line: int = 0
type: UnitType = UnitType.LINE
name: str = ""
content: str = ""
content_hash: str = ""
dependencies: List[str] = field(default_factory=list)
dependents: List[str] = field(default_factory=list)
state: UnitState = UnitState.UNKNOWN
cached_output: str = ""
cache_timestamp: int = 0
cache_valid: bool = False
def compute_hash(self) -> str:
"""计算内容哈希"""
normalized = '\n'.join(line.strip() for line in self.content.splitlines())
self.content_hash = hashlib.sha256(normalized.encode()).hexdigest()[:16]
return self.content_hash
def generate_id(self) -> str:
"""生成唯一ID"""
if not self.content_hash:
self.compute_hash()
self.id = f"{self.file_path}:{self.start_line}:{self.end_line}:{self.content_hash[:8]}"
return self.id
@classmethod
def from_code_block(cls, block: CodeBlock) -> 'CompilationUnit':
"""从 CodeBlock 创建"""
unit = cls()
unit.id = block.id
unit.file_path = block.id.split(':')[0] if ':' in block.id else ""
unit.start_line = block.start_line
unit.end_line = block.end_line
unit.content = block.content
unit.content_hash = block.content_hash
unit.name = block.name
unit.dependencies = block.dependencies.copy()
# 映射类型
type_map = {
BlockType.MODULE: UnitType.MODULE,
BlockType.IMPORT: UnitType.IMPORT,
BlockType.CLASS: UnitType.CLASS,
BlockType.FUNCTION: UnitType.FUNCTION,
BlockType.METHOD: UnitType.FUNCTION,
BlockType.DECORATOR: UnitType.DECORATOR,
BlockType.STATEMENT: UnitType.STATEMENT,
BlockType.ASSIGNMENT: UnitType.STATEMENT,
BlockType.EXPRESSION: UnitType.STATEMENT,
BlockType.CONTROL: UnitType.BLOCK,
BlockType.WITH: UnitType.BLOCK,
}
unit.type = type_map.get(block.type, UnitType.STATEMENT)
return unit
@dataclass
class Snapshot:
"""版本快照"""
file_path: str = ""
content_hash: str = ""
line_hashes: List[str] = field(default_factory=list)
units: Dict[str, CompilationUnit] = field(default_factory=dict)
timestamp: int = 0
@dataclass
class ChangeRecord:
"""变更记录"""
unit_id: str = ""
change_type: UnitState = UnitState.UNKNOWN
old_start_line: int = 0
old_end_line: int = 0
new_start_line: int = 0
new_end_line: int = 0
reason: str = ""
class ChangeDetector:
"""变更检测器"""
@staticmethod
def compute_hash(content: str) -> str:
"""计算内容哈希"""
return hashlib.sha256(content.encode()).hexdigest()[:16]
@staticmethod
def compute_line_hash(line: str) -> str:
"""计算行哈希(忽略首尾空白)"""
stripped = line.strip()
if not stripped:
return "empty"
return hashlib.sha256(stripped.encode()).hexdigest()[:16]
def create_snapshot(self, file_path: str, content: str) -> Snapshot:
"""创建快照"""
snap = Snapshot()
snap.file_path = file_path
snap.content_hash = self.compute_hash(content)
snap.timestamp = int(time.time() * 1000)
lines = content.splitlines()
snap.line_hashes = [self.compute_line_hash(line) for line in lines]
return snap
def get_changed_lines(self, old_snap: Snapshot, new_snap: Snapshot) -> List[int]:
"""获取变更的行号 (1-based)"""
# 使用 LCS 算法进行对比
lcs = self._compute_lcs(old_snap.line_hashes, new_snap.line_hashes)
# LCS 中新版本的行索引
lcs_new_indices = {pair[1] for pair in lcs}
# 不在 LCS 中的行即为变更的行
changed = []
for i in range(len(new_snap.line_hashes)):
if i not in lcs_new_indices:
changed.append(i + 1) # 1-based
return changed
def _compute_lcs(self, old_hashes: List[str], new_hashes: List[str]) -> List[Tuple[int, int]]:
"""计算最长公共子序列"""
m, n = len(old_hashes), len(new_hashes)
# DP 表
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if old_hashes[i - 1] == new_hashes[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
# 回溯找出 LCS 对应关系
lcs = []
i, j = m, n
while i > 0 and j > 0:
if old_hashes[i - 1] == new_hashes[j - 1]:
lcs.append((i - 1, j - 1))
i -= 1
j -= 1
elif dp[i - 1][j] > dp[i][j - 1]:
i -= 1
else:
j -= 1
lcs.reverse()
return lcs
def detect_changes(self, old_snap: Snapshot, new_snap: Snapshot) -> List[ChangeRecord]:
"""检测变更"""
records = []
old_ids = set(old_snap.units.keys())
new_ids = set(new_snap.units.keys())
# 删除的单元
for uid in old_ids - new_ids:
old_unit = old_snap.units[uid]
rec = ChangeRecord(
unit_id=uid,
change_type=UnitState.DELETED,
old_start_line=old_unit.start_line,
old_end_line=old_unit.end_line,
reason="unit deleted"
)
records.append(rec)
# 新增的单元
for uid in new_ids - old_ids:
new_unit = new_snap.units[uid]
rec = ChangeRecord(
unit_id=uid,
change_type=UnitState.ADDED,
new_start_line=new_unit.start_line,
new_end_line=new_unit.end_line,
reason="unit added"
)
records.append(rec)
# 修改的单元
for uid in old_ids & new_ids:
old_unit = old_snap.units[uid]
new_unit = new_snap.units[uid]
if old_unit.content_hash != new_unit.content_hash:
rec = ChangeRecord(
unit_id=uid,
change_type=UnitState.MODIFIED,
old_start_line=old_unit.start_line,
old_end_line=old_unit.end_line,
new_start_line=new_unit.start_line,
new_end_line=new_unit.end_line,
reason="content changed"
)
records.append(rec)
return records
class CompilationCache:
"""
编译缓存 V1.2
编译即缓存,缓存即编译
- 每次编译自动记录,全历史可追溯
- 缓存命中等同于零成本编译
- 集成预测预热
"""
def __init__(self, cache_dir: str):
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(parents=True, exist_ok=True)
self._cache: Dict[str, Dict] = {}
self._hits = 0
self._misses = 0
self._compile_history: List[Dict] = [] # 编译历史
self._access_sequence: List[str] = [] # 访问序列
self._predictions: Dict[str, List[str]] = {} # 预测模式
self._load()
def _load(self):
"""加载缓存"""
cache_file = self.cache_dir / "incremental_cache.json"
history_file = self.cache_dir / "compile_history.json"
patterns_file = self.cache_dir / "prediction_patterns.json"
if cache_file.exists():
try:
with open(cache_file, 'r', encoding='utf-8') as f:
self._cache = json.load(f)
except:
self._cache = {}
if history_file.exists():
try:
with open(history_file, 'r', encoding='utf-8') as f:
self._compile_history = json.load(f)
except:
self._compile_history = []
if patterns_file.exists():
try:
with open(patterns_file, 'r', encoding='utf-8') as f:
self._predictions = json.load(f)
except:
self._predictions = {}
def save(self):
"""保存缓存和历史"""
cache_file = self.cache_dir / "incremental_cache.json"
history_file = self.cache_dir / "compile_history.json"
patterns_file = self.cache_dir / "prediction_patterns.json"
with open(cache_file, 'w', encoding='utf-8') as f:
json.dump(self._cache, f, indent=2)
# 只保留最近10000条历史
with open(history_file, 'w', encoding='utf-8') as f:
json.dump(self._compile_history[-10000:], f, indent=2)
with open(patterns_file, 'w', encoding='utf-8') as f:
json.dump(self._predictions, f, indent=2)
def has(self, unit_id: str) -> bool:
return unit_id in self._cache
def get(self, unit_id: str) -> str:
"""缓存即编译 - 命中即零成本获得编译结果"""
if unit_id in self._cache:
self._hits += 1
# 记录访问序列
self._record_access(unit_id)
# 更新访问时间
self._cache[unit_id]['last_access'] = int(time.time() * 1000)
self._cache[unit_id]['access_count'] = self._cache[unit_id].get('access_count', 0) + 1
return self._cache[unit_id].get('output', '')
self._misses += 1
return ""
def put(self, unit_id: str, output: str, content_hash: str,
compile_time_ms: int = 0, file_path: str = "",
start_line: int = 0, end_line: int = 0):
"""编译即缓存 - 每次编译自动记录"""
timestamp = int(time.time() * 1000)
self._cache[unit_id] = {
'output': output,
'content_hash': content_hash,
'timestamp': timestamp,
'last_access': timestamp,
'access_count': 1,
'compile_time_ms': compile_time_ms,
'file_path': file_path,
'line_range': [start_line, end_line],
'size_bytes': len(output.encode('utf-8')),
}
# 记录编译历史
self._compile_history.append({
'unit_id': unit_id,
'content_hash': content_hash,
'timestamp': timestamp,
'compile_time_ms': compile_time_ms,
'file_path': file_path,
'action': 'compile'
})
# 记录访问序列
self._record_access(unit_id)
def _record_access(self, unit_id: str):
"""记录访问序列,用于预测"""
# 更新访问序列
self._access_sequence.append(unit_id)
if len(self._access_sequence) > 1000:
self._access_sequence = self._access_sequence[-500:]
# 学习访问模式
if len(self._access_sequence) >= 2:
prev_id = self._access_sequence[-2]
if prev_id != unit_id:
if prev_id not in self._predictions:
self._predictions[prev_id] = []
if unit_id not in self._predictions[prev_id]:
self._predictions[prev_id].append(unit_id)
# 限制预测列表长度
self._predictions[prev_id] = self._predictions[prev_id][:10]
def get_predictions(self, unit_id: str) -> List[str]:
"""获取预测的下一个可能访问的单元"""
return self._predictions.get(unit_id, [])
def invalidate(self, unit_id: str):
self._cache.pop(unit_id, None)
# 记录失效历史
self._compile_history.append({
'unit_id': unit_id,
'timestamp': int(time.time() * 1000),
'action': 'invalidate'
})
def invalidate_all(self):
self._cache.clear()
def is_valid(self, unit_id: str, current_hash: str) -> bool:
if unit_id not in self._cache:
return False
return self._cache[unit_id].get('content_hash') == current_hash
def get_compile_history(self, limit: int = 100) -> List[Dict]:
"""获取编译历史"""
return self._compile_history[-limit:]
def get_hot_units(self, limit: int = 20) -> List[Dict]:
"""获取热点单元(访问最频繁)"""
sorted_items = sorted(
self._cache.items(),
key=lambda x: x[1].get('access_count', 0),
reverse=True
)
return [
{'unit_id': k, 'access_count': v.get('access_count', 0),
'file': v.get('file_path', ''), 'lines': v.get('line_range', [])}
for k, v in sorted_items[:limit]
]
def get_stats(self) -> Dict[str, Any]:
"""获取统计信息"""
total_size = sum(e.get('size_bytes', 0) for e in self._cache.values())
total_compile_time = sum(e.get('compile_time_ms', 0) for e in self._cache.values())
return {
'version': '1.2',
'entries': len(self._cache),
'total_size_mb': total_size / (1024 * 1024),
'total_compile_time_ms': total_compile_time,
'hits': self._hits,
'misses': self._misses,
'hit_rate': self._hits / (self._hits + self._misses) if (self._hits + self._misses) > 0 else 0,
'history_count': len(self._compile_history),
'prediction_patterns': len(self._predictions),
}
@property
def hit_count(self) -> int:
return self._hits
@property
def miss_count(self) -> int:
return self._misses
class IncrementalCompiler:
"""
减量编译器 - 指哪编哪
核心功能:
1. 以最小语法块为编译单元
2. 变更检测 - 只定位修改的单元及受影响的关联单元
3. 仅对变更单元重新编译,未变更单元复用缓存
4. 边界触发器 - 自动扩展到函数/类边界
5. 按原始顺序拼接产物
"""
def __init__(self, cache_dir: str = ".sikuwa_cache"):
self.cache = CompilationCache(cache_dir)
self.detector = ChangeDetector()
self.analyzer = PythonAnalyzer()
self._units: Dict[str, CompilationUnit] = {}
self._file_units: Dict[str, List[str]] = {} # file -> unit_ids
self._snapshots: Dict[str, Snapshot] = {}
self._units_to_compile: List[str] = []
# 编译器回调
self._compile_callback: Optional[Callable[[CompilationUnit], str]] = None
def set_compiler(self, callback: Callable[[CompilationUnit], str]):
"""设置编译器回调"""
self._compile_callback = callback
def analyze_source(self, file_path: str, content: str) -> List[CompilationUnit]:
"""分析源代码,返回编译单元列表"""
blocks = self.analyzer.analyze(content, file_path)
units = [CompilationUnit.from_code_block(b) for b in blocks]
return units
def register_units(self, file_path: str, units: List[CompilationUnit]):
"""注册编译单元"""
# 移除旧单元
if file_path in self._file_units:
for uid in self._file_units[file_path]:
self._units.pop(uid, None)
# 添加新单元
self._file_units[file_path] = []
for unit in units:
self._units[unit.id] = unit
self._file_units[file_path].append(unit.id)
def update_source(self, file_path: str, new_content: str) -> List[ChangeRecord]:
"""
更新源代码并检测变更
返回变更记录列表
"""
# 分析新代码
new_units = self.analyze_source(file_path, new_content)
# 创建新快照
new_snap = self.detector.create_snapshot(file_path, new_content)
for unit in new_units:
new_snap.units[unit.id] = unit
changes = []
self._units_to_compile = []
# 检查是否有旧快照
old_snap = self._snapshots.get(file_path)
if old_snap:
# 获取变更的行
changed_lines = self.detector.get_changed_lines(old_snap, new_snap)
# 找出受影响的编译单元
affected_ids: Set[str] = set()
for line in changed_lines:
# 找出覆盖此行的单元
for unit in new_units:
if unit.start_line <= line <= unit.end_line:
affected_ids.add(unit.id)
unit.state = UnitState.MODIFIED
unit.cache_valid = False
# 传播依赖影响
affected_ids = self._propagate_dependencies(affected_ids, new_units)
# 扩展到边界
affected_ids = self._expand_to_boundaries(affected_ids, new_units)
# 生成变更记录
for uid in affected_ids:
unit = self._units.get(uid) or next((u for u in new_units if u.id == uid), None)
if unit:
rec = ChangeRecord(
unit_id=uid,
change_type=unit.state if unit.state != UnitState.UNKNOWN else UnitState.MODIFIED,
new_start_line=unit.start_line,
new_end_line=unit.end_line,
reason="content changed"
)
changes.append(rec)
self._units_to_compile.append(uid)
else:
# 首次分析,所有单元都需要编译
for unit in new_units:
unit.state = UnitState.ADDED
rec = ChangeRecord(
unit_id=unit.id,
change_type=UnitState.ADDED,
new_start_line=unit.start_line,
new_end_line=unit.end_line,
reason="first analysis"
)
changes.append(rec)
self._units_to_compile.append(unit.id)
# 注册单元并更新快照
self.register_units(file_path, new_units)
self._snapshots[file_path] = new_snap
return changes
def _propagate_dependencies(self, affected_ids: Set[str],
units: List[CompilationUnit]) -> Set[str]:
"""传播依赖影响"""
# 构建依赖图
dependents: Dict[str, List[str]] = {}
for unit in units:
for dep_id in unit.dependencies:
if dep_id not in dependents:
dependents[dep_id] = []
dependents[dep_id].append(unit.id)
# BFS 传播
queue = list(affected_ids)
visited = set(affected_ids)
while queue:
uid = queue.pop(0)
for dependent_id in dependents.get(uid, []):
if dependent_id not in visited:
visited.add(dependent_id)
queue.append(dependent_id)
# 标记为受影响
for unit in units:
if unit.id == dependent_id:
unit.state = UnitState.AFFECTED
unit.cache_valid = False
break
return visited
def _expand_to_boundaries(self, affected_ids: Set[str],
units: List[CompilationUnit]) -> Set[str]:
"""扩展到函数/类边界"""
expanded = set(affected_ids)
unit_map = {u.id: u for u in units}
for uid in list(affected_ids):
unit = unit_map.get(uid)
if not unit:
continue
# 如果在函数/类内部修改,需要重新编译整个结构
for other in units:
if other.id == uid:
continue
# 检查是否被包含
if (other.type in (UnitType.FUNCTION, UnitType.CLASS) and
other.start_line <= unit.start_line and
other.end_line >= unit.end_line):
expanded.add(other.id)
other.state = UnitState.AFFECTED
other.cache_valid = False
return expanded
def get_units_to_compile(self) -> List[str]:
"""获取需要编译的单元ID列表"""
return self._units_to_compile.copy()
def compile_unit(self, unit_id: str) -> str:
"""
编译单个单元
缓存即编译:缓存命中 = 零成本获得编译结果
"""
unit = self._units.get(unit_id)
if not unit:
return ""
# 检查缓存 - 缓存即编译
if unit.cache_valid or self.cache.is_valid(unit_id, unit.content_hash):
output = self.cache.get(unit_id)
if output:
unit.cached_output = output
unit.cache_valid = True
# 触发预测预热
self._predictive_warmup(unit_id)
return output
# 执行编译并计时
start_time = time.time()
if self._compile_callback:
output = self._compile_callback(unit)
else:
# 默认:直接返回源代码(用于测试)
output = unit.content
compile_time_ms = int((time.time() - start_time) * 1000)
# 编译即缓存:自动记录
self.mark_compiled(unit_id, output, compile_time_ms)
return output
def _predictive_warmup(self, unit_id: str):
"""预测性缓存预热"""
# 获取预测的下一个访问单元
predictions = self.cache.get_predictions(unit_id)
for pred_id in predictions[:2]: # 最多预热2个
if pred_id in self._units and not self.cache.has(pred_id):
# 加入待编译队列
if pred_id not in self._units_to_compile:
self._units_to_compile.append(pred_id)
def mark_compiled(self, unit_id: str, output: str, compile_time_ms: int = 0):
"""标记单元编译完成 - 编译即缓存"""
unit = self._units.get(unit_id)
if unit:
unit.cached_output = output
unit.cache_timestamp = int(time.time() * 1000)
unit.cache_valid = True
unit.state = UnitState.UNCHANGED
# 编译即缓存:记录完整信息
self.cache.put(
unit_id, output, unit.content_hash,
compile_time_ms=compile_time_ms,
file_path=unit.file_path,
start_line=unit.start_line,
end_line=unit.end_line
)
# 从待编译列表移除
if unit_id in self._units_to_compile:
self._units_to_compile.remove(unit_id)
def compile_all_pending(self) -> Dict[str, str]:
"""编译所有待编译单元"""
results = {}
for uid in self._units_to_compile.copy():
output = self.compile_unit(uid)
results[uid] = output
return results
def get_combined_output(self, file_path: str) -> str:
"""获取合并后的编译输出(按原始顺序拼接)"""
if file_path not in self._file_units:
return ""
# 按行号排序
unit_ids = self._file_units[file_path]
units = [self._units[uid] for uid in unit_ids if uid in self._units]
units.sort(key=lambda u: u.start_line)
# 拼接输出
outputs = []
for unit in units:
output = unit.cached_output
if not output and self.cache.has(unit.id):
output = self.cache.get(unit.id)
if output:
outputs.append(output)
return '\n'.join(outputs)
def get_stats(self) -> Dict[str, Any]:
"""获取统计信息"""
cache_stats = self.cache.get_stats()
return {
'total_units': len(self._units),
'pending_units': len(self._units_to_compile),
'files': len(self._file_units),
**cache_stats, # 包含缓存详细统计
}
def get_compile_history(self, limit: int = 100) -> List[Dict]:
"""获取编译历史"""
return self.cache.get_compile_history(limit)
def get_hot_units(self, limit: int = 20) -> List[Dict]:
"""获取热点单元"""
return self.cache.get_hot_units(limit)
def get_predictions(self, unit_id: str) -> List[str]:
"""获取预测的下一个访问单元"""
return self.cache.get_predictions(unit_id)
def save(self):
"""保存状态"""
self.cache.save()
def clear(self):
"""清空所有状态"""
self._units.clear()
self._file_units.clear()
self._snapshots.clear()
self._units_to_compile.clear()
self.cache.invalidate_all()
# 尝试导入 C++ 扩展
_cpp_available = False
try:
from .cpp import incremental_engine as _cpp_engine
_cpp_available = True
except ImportError:
pass
def create_incremental_compiler(cache_dir: str = ".sikuwa_cache",
prefer_cpp: bool = True) -> IncrementalCompiler:
"""
创建减量编译器实例
Args:
cache_dir: 缓存目录
prefer_cpp: 是否优先使用 C++ 实现
Returns:
IncrementalCompiler 实例
"""
# 目前返回 Python 实现
# TODO: 当 C++ 扩展可用时,返回包装器
return IncrementalCompiler(cache_dir)

View File

@@ -0,0 +1,45 @@
# sikuwa/incremental/cpp/CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(incremental_engine)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# 查找 Python 和 pybind11
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
find_package(pybind11 CONFIG QUIET)
if(NOT pybind11_FOUND)
# 如果没有安装 pybind11使用 FetchContent 下载
include(FetchContent)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.11.1
)
FetchContent_MakeAvailable(pybind11)
endif()
# 源文件
set(SOURCES
incremental_core.cpp
pybind_incremental.cpp
)
set(HEADERS
incremental_core.h
)
# 创建 Python 模块
pybind11_add_module(incremental_engine ${SOURCES} ${HEADERS})
# 优化选项
target_compile_options(incremental_engine PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-O3 -Wall -Wextra>
$<$<CXX_COMPILER_ID:Clang>:-O3 -Wall -Wextra>
$<$<CXX_COMPILER_ID:MSVC>:/O2 /W4>
)
# 安装
install(TARGETS incremental_engine DESTINATION .)

View File

@@ -0,0 +1,777 @@
// sikuwa/incremental/cpp/incremental_core.cpp
// 减量编译核心 - C++ 实现
#include "incremental_core.h"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstring>
#include <iomanip>
namespace sikuwa {
namespace incremental {
// ============================================================================
// 工具函数实现
// ============================================================================
// 简单的哈希函数 (FNV-1a)
static uint64_t fnv1a_hash(const char* data, size_t len) {
uint64_t hash = 14695981039346656037ULL;
for (size_t i = 0; i < len; ++i) {
hash ^= static_cast<uint64_t>(data[i]);
hash *= 1099511628211ULL;
}
return hash;
}
std::string generate_unit_id(const std::string& file_path, int start_line,
int end_line, const std::string& content_hash) {
std::ostringstream oss;
oss << file_path << ":" << start_line << ":" << end_line << ":"
<< content_hash.substr(0, 8);
return oss.str();
}
int64_t current_timestamp() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()
).count();
}
std::string read_file(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) return "";
std::ostringstream oss;
oss << file.rdbuf();
return oss.str();
}
void write_file(const std::string& path, const std::string& content) {
std::ofstream file(path);
if (file.is_open()) {
file << content;
}
}
std::vector<std::string> split_lines(const std::string& content) {
std::vector<std::string> lines;
std::istringstream iss(content);
std::string line;
while (std::getline(iss, line)) {
lines.push_back(line);
}
return lines;
}
std::string join_lines(const std::vector<std::string>& lines) {
std::ostringstream oss;
for (size_t i = 0; i < lines.size(); ++i) {
if (i > 0) oss << "\n";
oss << lines[i];
}
return oss.str();
}
// ============================================================================
// UnitManager 实现
// ============================================================================
UnitManager::UnitManager() {}
UnitManager::~UnitManager() {}
void UnitManager::add_unit(const CompilationUnit& unit) {
units_[unit.id] = unit;
file_units_[unit.file_path].push_back(unit.id);
}
void UnitManager::update_unit(const std::string& id, const CompilationUnit& unit) {
if (units_.find(id) != units_.end()) {
units_[id] = unit;
}
}
void UnitManager::remove_unit(const std::string& id) {
auto it = units_.find(id);
if (it != units_.end()) {
// 从文件索引中移除
auto& file_ids = file_units_[it->second.file_path];
file_ids.erase(std::remove(file_ids.begin(), file_ids.end(), id), file_ids.end());
// 从依赖关系中移除
for (const auto& dep_id : it->second.dependencies) {
auto dep_it = units_.find(dep_id);
if (dep_it != units_.end()) {
auto& dependents = dep_it->second.dependents;
dependents.erase(std::remove(dependents.begin(), dependents.end(), id),
dependents.end());
}
}
units_.erase(it);
}
}
CompilationUnit* UnitManager::get_unit(const std::string& id) {
auto it = units_.find(id);
return it != units_.end() ? &it->second : nullptr;
}
const CompilationUnit* UnitManager::get_unit(const std::string& id) const {
auto it = units_.find(id);
return it != units_.end() ? &it->second : nullptr;
}
std::vector<CompilationUnit*> UnitManager::get_units_by_file(const std::string& file_path) {
std::vector<CompilationUnit*> result;
auto it = file_units_.find(file_path);
if (it != file_units_.end()) {
for (const auto& id : it->second) {
if (auto* unit = get_unit(id)) {
result.push_back(unit);
}
}
}
// 按行号排序
std::sort(result.begin(), result.end(),
[](const CompilationUnit* a, const CompilationUnit* b) {
return a->start_line < b->start_line;
});
return result;
}
std::vector<CompilationUnit*> UnitManager::get_units_in_range(
const std::string& file_path, int start, int end) {
std::vector<CompilationUnit*> result;
auto units = get_units_by_file(file_path);
for (auto* unit : units) {
// 检查是否有交集
if (unit->start_line <= end && unit->end_line >= start) {
result.push_back(unit);
}
}
return result;
}
void UnitManager::add_dependency(const std::string& from_id, const std::string& to_id) {
auto* from_unit = get_unit(from_id);
auto* to_unit = get_unit(to_id);
if (from_unit && to_unit) {
// from 依赖 to
if (std::find(from_unit->dependencies.begin(), from_unit->dependencies.end(), to_id)
== from_unit->dependencies.end()) {
from_unit->dependencies.push_back(to_id);
}
// to 被 from 依赖
if (std::find(to_unit->dependents.begin(), to_unit->dependents.end(), from_id)
== to_unit->dependents.end()) {
to_unit->dependents.push_back(from_id);
}
}
}
void UnitManager::remove_dependency(const std::string& from_id, const std::string& to_id) {
auto* from_unit = get_unit(from_id);
auto* to_unit = get_unit(to_id);
if (from_unit) {
auto& deps = from_unit->dependencies;
deps.erase(std::remove(deps.begin(), deps.end(), to_id), deps.end());
}
if (to_unit) {
auto& dependents = to_unit->dependents;
dependents.erase(std::remove(dependents.begin(), dependents.end(), from_id),
dependents.end());
}
}
std::vector<std::string> UnitManager::get_dependencies(const std::string& id) const {
const auto* unit = get_unit(id);
return unit ? unit->dependencies : std::vector<std::string>{};
}
std::vector<std::string> UnitManager::get_dependents(const std::string& id) const {
const auto* unit = get_unit(id);
return unit ? unit->dependents : std::vector<std::string>{};
}
void UnitManager::collect_affected_recursive(const std::string& id,
std::unordered_set<std::string>& visited) const {
if (visited.count(id)) return;
visited.insert(id);
const auto* unit = get_unit(id);
if (!unit) return;
// 递归收集所有依赖此单元的单元
for (const auto& dependent_id : unit->dependents) {
collect_affected_recursive(dependent_id, visited);
}
}
std::vector<std::string> UnitManager::get_affected_units(const std::string& changed_id) const {
std::unordered_set<std::string> visited;
collect_affected_recursive(changed_id, visited);
visited.erase(changed_id); // 移除自身
return std::vector<std::string>(visited.begin(), visited.end());
}
void UnitManager::for_each(std::function<void(CompilationUnit&)> callback) {
for (auto& pair : units_) {
callback(pair.second);
}
}
void UnitManager::clear() {
units_.clear();
file_units_.clear();
}
std::string UnitManager::serialize() const {
std::ostringstream oss;
oss << units_.size() << "\n";
for (const auto& pair : units_) {
const auto& u = pair.second;
oss << u.id << "\t" << u.file_path << "\t" << u.start_line << "\t"
<< u.end_line << "\t" << static_cast<int>(u.type) << "\t"
<< u.name << "\t" << u.content_hash << "\t"
<< u.dependencies.size();
for (const auto& dep : u.dependencies) {
oss << "\t" << dep;
}
oss << "\n";
}
return oss.str();
}
void UnitManager::deserialize(const std::string& data) {
clear();
std::istringstream iss(data);
size_t count;
iss >> count;
iss.ignore();
for (size_t i = 0; i < count; ++i) {
std::string line;
std::getline(iss, line);
std::istringstream line_iss(line);
CompilationUnit u;
int type_int;
size_t dep_count;
std::getline(line_iss, u.id, '\t');
std::getline(line_iss, u.file_path, '\t');
line_iss >> u.start_line;
line_iss.ignore();
line_iss >> u.end_line;
line_iss.ignore();
line_iss >> type_int;
u.type = static_cast<UnitType>(type_int);
line_iss.ignore();
std::getline(line_iss, u.name, '\t');
std::getline(line_iss, u.content_hash, '\t');
line_iss >> dep_count;
for (size_t j = 0; j < dep_count; ++j) {
std::string dep;
line_iss.ignore();
std::getline(line_iss, dep, '\t');
if (!dep.empty()) {
u.dependencies.push_back(dep);
}
}
add_unit(u);
}
// 重建依赖关系
for (auto& pair : units_) {
for (const auto& dep_id : pair.second.dependencies) {
auto* dep_unit = get_unit(dep_id);
if (dep_unit) {
dep_unit->dependents.push_back(pair.first);
}
}
}
}
// ============================================================================
// ChangeDetector 实现
// ============================================================================
ChangeDetector::ChangeDetector() {}
ChangeDetector::~ChangeDetector() {}
std::string ChangeDetector::compute_hash(const std::string& content) {
uint64_t hash = fnv1a_hash(content.c_str(), content.size());
std::ostringstream oss;
oss << std::hex << std::setfill('0') << std::setw(16) << hash;
return oss.str();
}
std::string ChangeDetector::compute_line_hash(const std::string& line) {
// 去除首尾空白后计算哈希
size_t start = line.find_first_not_of(" \t\r\n");
size_t end = line.find_last_not_of(" \t\r\n");
if (start == std::string::npos) {
return "empty";
}
std::string trimmed = line.substr(start, end - start + 1);
return compute_hash(trimmed);
}
Snapshot ChangeDetector::create_snapshot(const std::string& file_path,
const std::string& content) {
Snapshot snap;
snap.file_path = file_path;
snap.content_hash = compute_hash(content);
snap.timestamp = current_timestamp();
auto lines = split_lines(content);
snap.line_hashes.reserve(lines.size());
for (const auto& line : lines) {
snap.line_hashes.push_back(compute_line_hash(line));
}
return snap;
}
std::vector<int> ChangeDetector::get_changed_lines(const Snapshot& old_snap,
const Snapshot& new_snap) {
std::vector<int> changed;
size_t old_size = old_snap.line_hashes.size();
size_t new_size = new_snap.line_hashes.size();
size_t max_size = std::max(old_size, new_size);
// 使用 LCS 算法进行精确对比
auto lcs = compute_lcs(old_snap.line_hashes, new_snap.line_hashes);
// 标记所有不在 LCS 中的行为变更
std::unordered_set<int> lcs_new_lines;
for (const auto& pair : lcs) {
lcs_new_lines.insert(pair.second);
}
for (size_t i = 0; i < new_size; ++i) {
if (lcs_new_lines.find(static_cast<int>(i)) == lcs_new_lines.end()) {
changed.push_back(static_cast<int>(i) + 1); // 1-based
}
}
return changed;
}
std::vector<std::pair<int, int>> ChangeDetector::compute_lcs(
const std::vector<std::string>& old_lines,
const std::vector<std::string>& new_lines) {
int m = static_cast<int>(old_lines.size());
int n = static_cast<int>(new_lines.size());
// DP 表
std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (old_lines[i - 1] == new_lines[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// 回溯找出 LCS 对应关系
std::vector<std::pair<int, int>> lcs;
int i = m, j = n;
while (i > 0 && j > 0) {
if (old_lines[i - 1] == new_lines[j - 1]) {
lcs.push_back({i - 1, j - 1});
--i; --j;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
--i;
} else {
--j;
}
}
std::reverse(lcs.begin(), lcs.end());
return lcs;
}
std::vector<ChangeRecord> ChangeDetector::detect_changes(const Snapshot& old_snap,
const Snapshot& new_snap) {
std::vector<ChangeRecord> records;
// 对比两个快照中的编译单元
std::unordered_set<std::string> old_ids, new_ids;
for (const auto& pair : old_snap.units) {
old_ids.insert(pair.first);
}
for (const auto& pair : new_snap.units) {
new_ids.insert(pair.first);
}
// 检测删除的单元
for (const auto& id : old_ids) {
if (new_ids.find(id) == new_ids.end()) {
ChangeRecord rec;
rec.unit_id = id;
rec.change_type = UnitState::DELETED;
const auto& old_unit = old_snap.units.at(id);
rec.old_start_line = old_unit.start_line;
rec.old_end_line = old_unit.end_line;
rec.reason = "unit deleted";
records.push_back(rec);
}
}
// 检测新增和修改的单元
for (const auto& pair : new_snap.units) {
const auto& new_unit = pair.second;
auto old_it = old_snap.units.find(pair.first);
if (old_it == old_snap.units.end()) {
// 新增
ChangeRecord rec;
rec.unit_id = pair.first;
rec.change_type = UnitState::ADDED;
rec.new_start_line = new_unit.start_line;
rec.new_end_line = new_unit.end_line;
rec.reason = "unit added";
records.push_back(rec);
} else {
// 检查是否修改
const auto& old_unit = old_it->second;
if (old_unit.content_hash != new_unit.content_hash) {
ChangeRecord rec;
rec.unit_id = pair.first;
rec.change_type = UnitState::MODIFIED;
rec.old_start_line = old_unit.start_line;
rec.old_end_line = old_unit.end_line;
rec.new_start_line = new_unit.start_line;
rec.new_end_line = new_unit.end_line;
rec.reason = "content changed";
records.push_back(rec);
}
}
}
return records;
}
// ============================================================================
// CompilationCache 实现
// ============================================================================
CompilationCache::CompilationCache(const std::string& cache_dir)
: cache_dir_(cache_dir), hits_(0), misses_(0) {}
CompilationCache::~CompilationCache() {
save();
}
bool CompilationCache::has(const std::string& unit_id) const {
return cache_.find(unit_id) != cache_.end();
}
std::string CompilationCache::get(const std::string& unit_id) const {
auto it = cache_.find(unit_id);
if (it != cache_.end()) {
++hits_;
return it->second.output;
}
++misses_;
return "";
}
void CompilationCache::put(const std::string& unit_id, const std::string& output,
const std::string& content_hash) {
CacheEntry entry;
entry.output = output;
entry.content_hash = content_hash;
entry.timestamp = current_timestamp();
cache_[unit_id] = entry;
}
void CompilationCache::invalidate(const std::string& unit_id) {
cache_.erase(unit_id);
}
void CompilationCache::invalidate_all() {
cache_.clear();
}
bool CompilationCache::is_valid(const std::string& unit_id,
const std::string& current_hash) const {
auto it = cache_.find(unit_id);
if (it == cache_.end()) return false;
return it->second.content_hash == current_hash;
}
void CompilationCache::save() {
std::string cache_file = cache_dir_ + "/incremental_cache.dat";
std::ofstream file(cache_file);
if (!file.is_open()) return;
file << cache_.size() << "\n";
for (const auto& pair : cache_) {
file << pair.first << "\n";
file << pair.second.content_hash << "\n";
file << pair.second.timestamp << "\n";
file << pair.second.output.size() << "\n";
file << pair.second.output;
}
}
void CompilationCache::load() {
std::string cache_file = cache_dir_ + "/incremental_cache.dat";
std::ifstream file(cache_file);
if (!file.is_open()) return;
size_t count;
file >> count;
file.ignore();
for (size_t i = 0; i < count; ++i) {
std::string unit_id, content_hash;
int64_t timestamp;
size_t output_size;
std::getline(file, unit_id);
std::getline(file, content_hash);
file >> timestamp >> output_size;
file.ignore();
std::string output(output_size, '\0');
file.read(&output[0], output_size);
CacheEntry entry;
entry.output = output;
entry.content_hash = content_hash;
entry.timestamp = timestamp;
cache_[unit_id] = entry;
}
}
// ============================================================================
// IncrementalEngine 实现
// ============================================================================
IncrementalEngine::IncrementalEngine(const std::string& cache_dir)
: cache_(cache_dir) {
cache_.load();
}
IncrementalEngine::~IncrementalEngine() {
save_state();
}
void IncrementalEngine::register_units(const std::string& file_path,
const std::vector<CompilationUnit>& units) {
// 移除该文件的旧单元
auto old_units = units_.get_units_by_file(file_path);
for (auto* old_unit : old_units) {
units_.remove_unit(old_unit->id);
}
// 添加新单元
for (const auto& unit : units) {
units_.add_unit(unit);
}
}
std::vector<ChangeRecord> IncrementalEngine::update_source(
const std::string& file_path, const std::string& new_content) {
// 创建新快照
Snapshot new_snap = detector_.create_snapshot(file_path, new_content);
// 获取旧快照
auto old_it = snapshots_.find(file_path);
std::vector<ChangeRecord> changes;
if (old_it != snapshots_.end()) {
// 获取变更的行
auto changed_lines = detector_.get_changed_lines(old_it->second, new_snap);
// 找出受影响的编译单元
std::unordered_set<std::string> affected_ids;
for (int line : changed_lines) {
auto units = units_.get_units_in_range(file_path, line, line);
for (auto* unit : units) {
affected_ids.insert(unit->id);
// 标记为已修改
unit->state = UnitState::MODIFIED;
unit->cache_valid = false;
// 获取所有受影响的依赖单元
auto dependents = units_.get_affected_units(unit->id);
for (const auto& dep_id : dependents) {
affected_ids.insert(dep_id);
auto* dep_unit = units_.get_unit(dep_id);
if (dep_unit) {
dep_unit->state = UnitState::AFFECTED;
dep_unit->cache_valid = false;
}
}
}
}
// 扩展到完整边界
std::vector<std::string> ids_to_expand(affected_ids.begin(), affected_ids.end());
expand_to_boundaries(file_path, ids_to_expand);
affected_ids = std::unordered_set<std::string>(ids_to_expand.begin(), ids_to_expand.end());
// 生成变更记录
for (const auto& id : affected_ids) {
auto* unit = units_.get_unit(id);
if (unit) {
ChangeRecord rec;
rec.unit_id = id;
rec.change_type = unit->state;
rec.new_start_line = unit->start_line;
rec.new_end_line = unit->end_line;
changes.push_back(rec);
}
}
// 需要重新编译的单元
units_to_compile_.clear();
for (const auto& id : affected_ids) {
units_to_compile_.push_back(id);
}
} else {
// 首次编译,所有单元都需要编译
auto units = units_.get_units_by_file(file_path);
for (auto* unit : units) {
unit->state = UnitState::ADDED;
units_to_compile_.push_back(unit->id);
ChangeRecord rec;
rec.unit_id = unit->id;
rec.change_type = UnitState::ADDED;
rec.new_start_line = unit->start_line;
rec.new_end_line = unit->end_line;
changes.push_back(rec);
}
}
// 更新快照
new_snap.units = std::unordered_map<std::string, CompilationUnit>();
for (auto* unit : units_.get_units_by_file(file_path)) {
new_snap.units[unit->id] = *unit;
}
snapshots_[file_path] = new_snap;
return changes;
}
std::vector<std::string> IncrementalEngine::get_units_to_compile() const {
return units_to_compile_;
}
void IncrementalEngine::mark_compiled(const std::string& unit_id,
const std::string& output) {
auto* unit = units_.get_unit(unit_id);
if (unit) {
unit->cached_output = output;
unit->cache_timestamp = current_timestamp();
unit->cache_valid = true;
unit->state = UnitState::UNCHANGED;
// 更新缓存
cache_.put(unit_id, output, unit->content_hash);
}
// 从待编译列表中移除
units_to_compile_.erase(
std::remove(units_to_compile_.begin(), units_to_compile_.end(), unit_id),
units_to_compile_.end()
);
}
std::string IncrementalEngine::get_combined_output(const std::string& file_path) const {
std::ostringstream oss;
auto units = const_cast<UnitManager&>(units_).get_units_by_file(file_path);
// 按行号顺序排列
std::sort(units.begin(), units.end(),
[](const CompilationUnit* a, const CompilationUnit* b) {
return a->start_line < b->start_line;
});
for (size_t i = 0; i < units.size(); ++i) {
const auto* unit = units[i];
// 优先使用缓存
std::string output;
if (unit->cache_valid) {
output = unit->cached_output;
} else if (cache_.is_valid(unit->id, unit->content_hash)) {
output = cache_.get(unit->id);
}
if (!output.empty()) {
if (i > 0) oss << "\n";
oss << output;
}
}
return oss.str();
}
void IncrementalEngine::expand_to_boundaries(const std::string& file_path,
std::vector<std::string>& unit_ids) {
std::unordered_set<std::string> expanded(unit_ids.begin(), unit_ids.end());
for (const auto& id : unit_ids) {
auto* unit = units_.get_unit(id);
if (!unit) continue;
// 对于函数、类等结构,确保整个结构都被包含
if (unit->type == UnitType::FUNCTION || unit->type == UnitType::CLASS) {
// 已经是完整结构,不需要扩展
continue;
}
// 检查是否在某个大结构内
auto all_units = units_.get_units_by_file(file_path);
for (auto* parent : all_units) {
if (parent->id == id) continue;
// 如果当前单元在父结构范围内
if (parent->start_line <= unit->start_line &&
parent->end_line >= unit->end_line) {
// 父结构是函数或类,需要重新编译整个结构
if (parent->type == UnitType::FUNCTION || parent->type == UnitType::CLASS) {
expanded.insert(parent->id);
parent->state = UnitState::AFFECTED;
parent->cache_valid = false;
}
}
}
}
unit_ids = std::vector<std::string>(expanded.begin(), expanded.end());
}
void IncrementalEngine::save_state() {
cache_.save();
// 保存单元状态
std::string state_file = cache_.cache().empty() ? "incremental_state.dat"
: cache_dir_ + "/incremental_state.dat";
// Note: cache_dir_ is private, so we'll save alongside cache
}
void IncrementalEngine::load_state() {
cache_.load();
}
} // namespace incremental
} // namespace sikuwa

View File

@@ -0,0 +1,283 @@
// sikuwa/incremental/cpp/incremental_core.h
// 减量编译核心 - C++ 实现高性能组件
// 指哪编哪:只编译源码改变的部分
#ifndef SIKUWA_INCREMENTAL_CORE_H
#define SIKUWA_INCREMENTAL_CORE_H
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <memory>
#include <functional>
#include <optional>
#include <chrono>
namespace sikuwa {
namespace incremental {
// ============================================================================
// 编译单元类型
// ============================================================================
enum class UnitType {
LINE, // 单行
STATEMENT, // 语句
FUNCTION, // 函数
CLASS, // 类
MODULE, // 模块级
IMPORT, // 导入语句
DECORATOR, // 装饰器
BLOCK // 代码块
};
// ============================================================================
// 编译单元状态
// ============================================================================
enum class UnitState {
UNKNOWN, // 未知
UNCHANGED, // 未变更
MODIFIED, // 已修改
ADDED, // 新增
DELETED, // 已删除
AFFECTED // 受影响(依赖项变更)
};
// ============================================================================
// 编译单元 - 最小编译粒度
// ============================================================================
struct CompilationUnit {
std::string id; // 唯一标识: file:start_line:end_line:hash
std::string file_path; // 源文件路径
int start_line; // 起始行 (1-based)
int end_line; // 结束行 (1-based)
UnitType type; // 单元类型
std::string name; // 名称 (函数名/类名等)
std::string content_hash; // 内容哈希
std::vector<std::string> dependencies; // 依赖的单元ID列表
std::vector<std::string> dependents; // 被依赖的单元ID列表
UnitState state; // 当前状态
// 缓存相关
std::string cached_output; // 缓存的编译产物
int64_t cache_timestamp; // 缓存时间戳
bool cache_valid; // 缓存是否有效
CompilationUnit()
: start_line(0), end_line(0), type(UnitType::LINE),
state(UnitState::UNKNOWN), cache_timestamp(0), cache_valid(false) {}
};
// ============================================================================
// 版本快照 - 用于变更检测
// ============================================================================
struct Snapshot {
std::string file_path;
std::string content_hash; // 整体内容哈希
std::vector<std::string> line_hashes; // 每行哈希
std::unordered_map<std::string, CompilationUnit> units; // 编译单元
int64_t timestamp;
Snapshot() : timestamp(0) {}
};
// ============================================================================
// 变更记录
// ============================================================================
struct ChangeRecord {
std::string unit_id;
UnitState change_type;
int old_start_line;
int old_end_line;
int new_start_line;
int new_end_line;
std::string reason; // 变更原因
};
// ============================================================================
// 编译单元管理器 - 管理所有编译单元
// ============================================================================
class UnitManager {
public:
UnitManager();
~UnitManager();
// 添加/更新编译单元
void add_unit(const CompilationUnit& unit);
void update_unit(const std::string& id, const CompilationUnit& unit);
void remove_unit(const std::string& id);
// 查询
CompilationUnit* get_unit(const std::string& id);
const CompilationUnit* get_unit(const std::string& id) const;
std::vector<CompilationUnit*> get_units_by_file(const std::string& file_path);
std::vector<CompilationUnit*> get_units_in_range(const std::string& file_path, int start, int end);
// 依赖关系
void add_dependency(const std::string& from_id, const std::string& to_id);
void remove_dependency(const std::string& from_id, const std::string& to_id);
std::vector<std::string> get_dependencies(const std::string& id) const;
std::vector<std::string> get_dependents(const std::string& id) const;
std::vector<std::string> get_affected_units(const std::string& changed_id) const;
// 遍历
void for_each(std::function<void(CompilationUnit&)> callback);
size_t size() const { return units_.size(); }
void clear();
// 序列化
std::string serialize() const;
void deserialize(const std::string& data);
private:
std::unordered_map<std::string, CompilationUnit> units_;
std::unordered_map<std::string, std::vector<std::string>> file_units_; // file -> unit_ids
// 递归获取所有受影响的单元
void collect_affected_recursive(const std::string& id,
std::unordered_set<std::string>& visited) const;
};
// ============================================================================
// 变更检测器 - 检测源码变更
// ============================================================================
class ChangeDetector {
public:
ChangeDetector();
~ChangeDetector();
// 创建快照
Snapshot create_snapshot(const std::string& file_path, const std::string& content);
// 检测变更
std::vector<ChangeRecord> detect_changes(const Snapshot& old_snap, const Snapshot& new_snap);
// 定位变更行
std::vector<int> get_changed_lines(const Snapshot& old_snap, const Snapshot& new_snap);
// 计算哈希
static std::string compute_hash(const std::string& content);
static std::string compute_line_hash(const std::string& line);
private:
// LCS 算法找出变更
std::vector<std::pair<int, int>> compute_lcs(const std::vector<std::string>& old_lines,
const std::vector<std::string>& new_lines);
};
// ============================================================================
// 编译缓存 - 缓存编译产物
// ============================================================================
class CompilationCache {
public:
CompilationCache(const std::string& cache_dir);
~CompilationCache();
// 缓存操作
bool has(const std::string& unit_id) const;
std::string get(const std::string& unit_id) const;
void put(const std::string& unit_id, const std::string& output, const std::string& content_hash);
void invalidate(const std::string& unit_id);
void invalidate_all();
// 验证缓存
bool is_valid(const std::string& unit_id, const std::string& current_hash) const;
// 持久化
void save();
void load();
// 统计
size_t size() const { return cache_.size(); }
size_t hit_count() const { return hits_; }
size_t miss_count() const { return misses_; }
private:
struct CacheEntry {
std::string output;
std::string content_hash;
int64_t timestamp;
};
std::string cache_dir_;
std::unordered_map<std::string, CacheEntry> cache_;
mutable size_t hits_;
mutable size_t misses_;
};
// ============================================================================
// 减量编译引擎
// ============================================================================
class IncrementalEngine {
public:
IncrementalEngine(const std::string& cache_dir);
~IncrementalEngine();
// 注册编译单元
void register_units(const std::string& file_path,
const std::vector<CompilationUnit>& units);
// 更新源码并检测变更
std::vector<ChangeRecord> update_source(const std::string& file_path,
const std::string& new_content);
// 获取需要重新编译的单元
std::vector<std::string> get_units_to_compile() const;
// 标记单元编译完成
void mark_compiled(const std::string& unit_id, const std::string& output);
// 获取编译结果(按顺序拼接)
std::string get_combined_output(const std::string& file_path) const;
// 缓存管理
CompilationCache& cache() { return cache_; }
const CompilationCache& cache() const { return cache_; }
// 单元管理
UnitManager& units() { return units_; }
const UnitManager& units() const { return units_; }
// 状态
void save_state();
void load_state();
private:
UnitManager units_;
ChangeDetector detector_;
CompilationCache cache_;
std::unordered_map<std::string, Snapshot> snapshots_; // file -> snapshot
std::vector<std::string> units_to_compile_;
// 扩展编译范围到完整结构
void expand_to_boundaries(const std::string& file_path,
std::vector<std::string>& unit_ids);
};
// ============================================================================
// 工具函数
// ============================================================================
// 生成单元ID
std::string generate_unit_id(const std::string& file_path, int start_line,
int end_line, const std::string& content_hash);
// 获取当前时间戳
int64_t current_timestamp();
// 读取文件内容
std::string read_file(const std::string& path);
// 写入文件内容
void write_file(const std::string& path, const std::string& content);
// 分割行
std::vector<std::string> split_lines(const std::string& content);
// 合并行
std::string join_lines(const std::vector<std::string>& lines);
} // namespace incremental
} // namespace sikuwa
#endif // SIKUWA_INCREMENTAL_CORE_H

View File

@@ -0,0 +1,130 @@
// sikuwa/incremental/cpp/pybind_incremental.cpp
// Python 绑定 - 使用 pybind11
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "incremental_core.h"
namespace py = pybind11;
using namespace sikuwa::incremental;
PYBIND11_MODULE(incremental_engine, m) {
m.doc() = "Sikuwa 减量编译引擎 - 指哪编哪";
// 枚举类型
py::enum_<UnitType>(m, "UnitType")
.value("LINE", UnitType::LINE)
.value("STATEMENT", UnitType::STATEMENT)
.value("FUNCTION", UnitType::FUNCTION)
.value("CLASS", UnitType::CLASS)
.value("MODULE", UnitType::MODULE)
.value("IMPORT", UnitType::IMPORT)
.value("DECORATOR", UnitType::DECORATOR)
.value("BLOCK", UnitType::BLOCK);
py::enum_<UnitState>(m, "UnitState")
.value("UNKNOWN", UnitState::UNKNOWN)
.value("UNCHANGED", UnitState::UNCHANGED)
.value("MODIFIED", UnitState::MODIFIED)
.value("ADDED", UnitState::ADDED)
.value("DELETED", UnitState::DELETED)
.value("AFFECTED", UnitState::AFFECTED);
// CompilationUnit
py::class_<CompilationUnit>(m, "CompilationUnit")
.def(py::init<>())
.def_readwrite("id", &CompilationUnit::id)
.def_readwrite("file_path", &CompilationUnit::file_path)
.def_readwrite("start_line", &CompilationUnit::start_line)
.def_readwrite("end_line", &CompilationUnit::end_line)
.def_readwrite("type", &CompilationUnit::type)
.def_readwrite("name", &CompilationUnit::name)
.def_readwrite("content_hash", &CompilationUnit::content_hash)
.def_readwrite("dependencies", &CompilationUnit::dependencies)
.def_readwrite("dependents", &CompilationUnit::dependents)
.def_readwrite("state", &CompilationUnit::state)
.def_readwrite("cached_output", &CompilationUnit::cached_output)
.def_readwrite("cache_valid", &CompilationUnit::cache_valid);
// ChangeRecord
py::class_<ChangeRecord>(m, "ChangeRecord")
.def(py::init<>())
.def_readwrite("unit_id", &ChangeRecord::unit_id)
.def_readwrite("change_type", &ChangeRecord::change_type)
.def_readwrite("old_start_line", &ChangeRecord::old_start_line)
.def_readwrite("old_end_line", &ChangeRecord::old_end_line)
.def_readwrite("new_start_line", &ChangeRecord::new_start_line)
.def_readwrite("new_end_line", &ChangeRecord::new_end_line)
.def_readwrite("reason", &ChangeRecord::reason);
// Snapshot
py::class_<Snapshot>(m, "Snapshot")
.def(py::init<>())
.def_readwrite("file_path", &Snapshot::file_path)
.def_readwrite("content_hash", &Snapshot::content_hash)
.def_readwrite("line_hashes", &Snapshot::line_hashes)
.def_readwrite("timestamp", &Snapshot::timestamp);
// UnitManager
py::class_<UnitManager>(m, "UnitManager")
.def(py::init<>())
.def("add_unit", &UnitManager::add_unit)
.def("update_unit", &UnitManager::update_unit)
.def("remove_unit", &UnitManager::remove_unit)
.def("get_unit", py::overload_cast<const std::string&>(&UnitManager::get_unit),
py::return_value_policy::reference)
.def("get_units_by_file", &UnitManager::get_units_by_file,
py::return_value_policy::reference)
.def("get_units_in_range", &UnitManager::get_units_in_range,
py::return_value_policy::reference)
.def("add_dependency", &UnitManager::add_dependency)
.def("remove_dependency", &UnitManager::remove_dependency)
.def("get_dependencies", &UnitManager::get_dependencies)
.def("get_dependents", &UnitManager::get_dependents)
.def("get_affected_units", &UnitManager::get_affected_units)
.def("size", &UnitManager::size)
.def("clear", &UnitManager::clear)
.def("serialize", &UnitManager::serialize)
.def("deserialize", &UnitManager::deserialize);
// ChangeDetector
py::class_<ChangeDetector>(m, "ChangeDetector")
.def(py::init<>())
.def("create_snapshot", &ChangeDetector::create_snapshot)
.def("detect_changes", &ChangeDetector::detect_changes)
.def("get_changed_lines", &ChangeDetector::get_changed_lines)
.def_static("compute_hash", &ChangeDetector::compute_hash)
.def_static("compute_line_hash", &ChangeDetector::compute_line_hash);
// CompilationCache
py::class_<CompilationCache>(m, "CompilationCache")
.def(py::init<const std::string&>())
.def("has", &CompilationCache::has)
.def("get", &CompilationCache::get)
.def("put", &CompilationCache::put)
.def("invalidate", &CompilationCache::invalidate)
.def("invalidate_all", &CompilationCache::invalidate_all)
.def("is_valid", &CompilationCache::is_valid)
.def("save", &CompilationCache::save)
.def("load", &CompilationCache::load)
.def("size", &CompilationCache::size)
.def("hit_count", &CompilationCache::hit_count)
.def("miss_count", &CompilationCache::miss_count);
// IncrementalEngine
py::class_<IncrementalEngine>(m, "IncrementalEngine")
.def(py::init<const std::string&>())
.def("register_units", &IncrementalEngine::register_units)
.def("update_source", &IncrementalEngine::update_source)
.def("get_units_to_compile", &IncrementalEngine::get_units_to_compile)
.def("mark_compiled", &IncrementalEngine::mark_compiled)
.def("get_combined_output", &IncrementalEngine::get_combined_output)
.def("save_state", &IncrementalEngine::save_state)
.def("load_state", &IncrementalEngine::load_state);
// 工具函数
m.def("generate_unit_id", &generate_unit_id);
m.def("compute_hash", &ChangeDetector::compute_hash);
m.def("split_lines", &split_lines);
m.def("join_lines", &join_lines);
}

295
incremental/demo.py Normal file
View File

@@ -0,0 +1,295 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
减量编译演示 - Sikuwa Incremental Compilation Demo
展示"指哪编哪"的精准编译能力
"""
import tempfile
from pathlib import Path
from incremental import (
IncrementalCompiler,
PythonAnalyzer,
BlockType
)
def demo_analyzer():
"""演示代码分析器"""
print("=" * 60)
print("1. 代码分析器演示")
print("=" * 60)
analyzer = PythonAnalyzer()
code = '''
import os
from pathlib import Path
x = 10
y = 20
def add(a, b):
"""加法"""
return a + b
def multiply(a, b):
"""乘法"""
return a * b
class Calculator:
"""计算器类"""
def __init__(self):
self.history = []
def calculate(self, op, a, b):
if op == '+':
result = add(a, b)
elif op == '*':
result = multiply(a, b)
self.history.append(result)
return result
'''
blocks = analyzer.analyze(code, "demo.py")
print(f"\n检测到 {len(blocks)} 个代码块:\n")
for block in blocks:
type_name = block.type.name.lower()
deps = ', '.join(block.references[:5]) if block.references else ''
print(f" [{type_name:10}] {block.name:20}{block.start_line:2}-{block.end_line:2} 依赖: {deps}")
def demo_change_detection():
"""演示变更检测"""
print("\n" + "=" * 60)
print("2. 变更检测演示")
print("=" * 60)
with tempfile.TemporaryDirectory() as tmpdir:
compiler = IncrementalCompiler(tmpdir)
# 模拟编译器
compile_count = [0]
def mock_compile(unit):
compile_count[0] += 1
return f"COMPILED: {unit.name or 'unknown'}"
compiler.set_compiler(mock_compile)
# 初始代码
code_v1 = '''
def hello():
print("Hello")
def world():
print("World")
def main():
hello()
world()
'''
print("\n[v1] 初始代码:")
compiler.analyze_source("demo.py", code_v1)
changes = compiler.update_source("demo.py", code_v1)
print(f" 检测到 {len(changes)} 个新增单元")
outputs = compiler.compile_all_pending()
print(f" 编译了 {compile_count[0]} 个单元")
# 修改一个函数
code_v2 = '''
def hello():
print("Hello, World!") # 修改了这行
def world():
print("World")
def main():
hello()
world()
'''
compile_count[0] = 0
print("\n[v2] 修改 hello 函数:")
changes = compiler.update_source("demo.py", code_v2)
print(f" 检测到 {len(changes)} 个变更单元")
for ch in changes:
print(f" - {ch.unit_id[:40]}... ({ch.change_type.name})")
outputs = compiler.compile_all_pending()
print(f" 只编译了 {compile_count[0]} 个单元 (其他使用缓存)")
# 添加新函数
code_v3 = '''
def hello():
print("Hello, World!")
def world():
print("World")
def greet(name):
print(f"Hi, {name}!")
def main():
hello()
world()
greet("Sikuwa")
'''
compile_count[0] = 0
print("\n[v3] 添加 greet 函数:")
changes = compiler.update_source("demo.py", code_v3)
print(f" 检测到 {len(changes)} 个变更单元")
outputs = compiler.compile_all_pending()
print(f" 编译了 {compile_count[0]} 个新/变更单元")
# 统计
stats = compiler.get_stats()
print(f"\n统计: 缓存命中 {stats.get('cache_hits', 0)}, 总编译 {stats.get('total_compiled', 0)}")
def demo_dependency_tracking():
"""演示依赖追踪"""
print("\n" + "=" * 60)
print("3. 依赖追踪演示")
print("=" * 60)
with tempfile.TemporaryDirectory() as tmpdir:
compiler = IncrementalCompiler(tmpdir)
affected_units = []
def mock_compile(unit):
affected_units.append(unit.name or unit.id[:20])
return f"COMPILED"
compiler.set_compiler(mock_compile)
code_v1 = '''
# 基础配置
CONFIG = {"debug": False}
def get_config():
return CONFIG
def process():
cfg = get_config()
return cfg["debug"]
def main():
result = process()
print(result)
'''
print("\n初始编译...")
compiler.analyze_source("demo.py", code_v1)
compiler.update_source("demo.py", code_v1)
compiler.compile_all_pending()
# 修改 CONFIG
code_v2 = '''
# 基础配置
CONFIG = {"debug": True} # 修改
def get_config():
return CONFIG
def process():
cfg = get_config()
return cfg["debug"]
def main():
result = process()
print(result)
'''
affected_units.clear()
print("\n修改 CONFIG 后:")
changes = compiler.update_source("demo.py", code_v2)
# 显示依赖传播
print(" 受影响的单元链:")
print(" CONFIG (修改) → get_config (依赖CONFIG) → process (依赖get_config)")
compiler.compile_all_pending()
print(f" 重新编译: {', '.join(affected_units) if affected_units else ''}")
def demo_output_combination():
"""演示输出合并"""
print("\n" + "=" * 60)
print("4. 输出合并演示")
print("=" * 60)
with tempfile.TemporaryDirectory() as tmpdir:
compiler = IncrementalCompiler(tmpdir)
# 转换为 C 风格伪代码
def to_pseudo_c(unit):
lines = unit.content.strip().split('\n')
result = []
for line in lines:
line = line.strip()
if line.startswith('def '):
# def func(): -> void func() {
name = line[4:line.index('(')]
result.append(f"void {name}() {{")
elif line.startswith('print('):
# print("x") -> printf("x");
content = line[6:-1]
result.append(f" printf({content});")
elif line == '':
continue
else:
result.append(f" // {line}")
if result and not result[-1].endswith('}'):
result.append("}")
return '\n'.join(result)
compiler.set_compiler(to_pseudo_c)
code = '''
def hello():
print("Hello")
def world():
print("World")
'''
compiler.analyze_source("demo.py", code)
compiler.update_source("demo.py", code)
compiler.compile_all_pending()
combined = compiler.get_combined_output("demo.py")
print("\n原始 Python 代码:")
print(code)
print("合并后的编译产物:")
print(combined)
def main():
"""主函数"""
print("\n" + "=" * 60)
print("Sikuwa 减量编译系统演示")
print("指哪编哪 - 精准编译,高效开发")
print("=" * 60)
demo_analyzer()
demo_change_detection()
demo_dependency_tracking()
demo_output_combination()
print("\n" + "=" * 60)
print("演示完成!")
print("=" * 60)
if __name__ == '__main__':
main()

556
incremental/smart_cache.py Normal file
View File

@@ -0,0 +1,556 @@
# sikuwa/incremental/smart_cache.py
"""
智能缓存系统 V1.2
编译即缓存,缓存即编译,预测缓存预热
深度集成减量编译引擎,实现:
1. 编译即缓存 - 每次编译自动持久化,全历史可追溯
2. 缓存即编译 - 缓存命中等同于零成本编译
3. 预测缓存预热 - 基于访问模式和依赖图预测并预编译
"""
import hashlib
import json
import os
import time
import threading
import queue
from enum import Enum, auto
from dataclasses import dataclass, field, asdict
from typing import Dict, List, Set, Optional, Tuple, Callable, Any
from pathlib import Path
from collections import OrderedDict
class CacheEventType(Enum):
"""缓存事件类型"""
HIT = auto() # 命中
MISS = auto() # 未命中
WRITE = auto() # 写入
EVICT = auto() # 淘汰
WARMUP = auto() # 预热
PREDICT = auto() # 预测
@dataclass
class CacheEntry:
"""缓存条目"""
key: str = ""
content_hash: str = ""
output: str = ""
timestamp: int = 0
access_count: int = 0
last_access: int = 0
dependencies: List[str] = field(default_factory=list)
file_path: str = ""
line_range: Tuple[int, int] = (0, 0)
compile_time_ms: int = 0
size_bytes: int = 0
def touch(self):
"""更新访问信息"""
self.access_count += 1
self.last_access = int(time.time() * 1000)
def to_dict(self) -> dict:
return {
'key': self.key,
'content_hash': self.content_hash,
'output': self.output,
'timestamp': self.timestamp,
'access_count': self.access_count,
'last_access': self.last_access,
'dependencies': self.dependencies,
'file_path': self.file_path,
'line_range': list(self.line_range),
'compile_time_ms': self.compile_time_ms,
'size_bytes': self.size_bytes,
}
@classmethod
def from_dict(cls, data: dict) -> 'CacheEntry':
entry = cls()
entry.key = data.get('key', '')
entry.content_hash = data.get('content_hash', '')
entry.output = data.get('output', '')
entry.timestamp = data.get('timestamp', 0)
entry.access_count = data.get('access_count', 0)
entry.last_access = data.get('last_access', 0)
entry.dependencies = data.get('dependencies', [])
entry.file_path = data.get('file_path', '')
line_range = data.get('line_range', [0, 0])
entry.line_range = tuple(line_range) if isinstance(line_range, list) else line_range
entry.compile_time_ms = data.get('compile_time_ms', 0)
entry.size_bytes = data.get('size_bytes', 0)
return entry
@dataclass
class CacheEvent:
"""缓存事件记录"""
event_type: CacheEventType
key: str
timestamp: int
details: str = ""
@dataclass
class AccessPattern:
"""访问模式记录"""
key: str
access_sequence: List[str] = field(default_factory=list) # 之后访问的键
frequency: int = 0
def record_next(self, next_key: str):
"""记录后续访问"""
if next_key not in self.access_sequence:
self.access_sequence.append(next_key)
self.frequency += 1
class SmartCache:
"""
智能缓存系统 V1.2
核心特性:
- LRU 淘汰策略 + 访问频率权重
- 全历史编译记录持久化
- 基于访问模式的预测预热
- 依赖图感知的缓存失效
- 后台异步预热线程
"""
def __init__(self,
cache_dir: str = ".sikuwa_cache",
max_entries: int = 10000,
max_size_mb: int = 500,
enable_warmup: bool = True):
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(parents=True, exist_ok=True)
self.max_entries = max_entries
self.max_size_bytes = max_size_mb * 1024 * 1024
self.enable_warmup = enable_warmup
# 主缓存存储 (LRU)
self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
self._total_size = 0
# 统计信息
self._hits = 0
self._misses = 0
self._evictions = 0
self._warmups = 0
# 事件日志
self._events: List[CacheEvent] = []
self._max_events = 10000
# 访问模式追踪
self._last_accessed_key: Optional[str] = None
self._access_patterns: Dict[str, AccessPattern] = {}
# 编译器回调(用于预热)
self._compiler_callback: Optional[Callable] = None
# 预热队列和线程
self._warmup_queue: queue.Queue = queue.Queue()
self._warmup_thread: Optional[threading.Thread] = None
self._warmup_running = False
# 加载持久化数据
self._load()
# 启动预热线程
if enable_warmup:
self._start_warmup_thread()
def _load(self):
"""加载持久化缓存"""
cache_file = self.cache_dir / "smart_cache_v1.2.json"
patterns_file = self.cache_dir / "access_patterns.json"
if cache_file.exists():
try:
with open(cache_file, 'r', encoding='utf-8') as f:
data = json.load(f)
for entry_data in data.get('entries', []):
entry = CacheEntry.from_dict(entry_data)
self._cache[entry.key] = entry
self._total_size += entry.size_bytes
except Exception:
pass
if patterns_file.exists():
try:
with open(patterns_file, 'r', encoding='utf-8') as f:
data = json.load(f)
for key, pattern_data in data.items():
self._access_patterns[key] = AccessPattern(
key=key,
access_sequence=pattern_data.get('sequence', []),
frequency=pattern_data.get('frequency', 0)
)
except Exception:
pass
def save(self):
"""保存缓存到磁盘"""
cache_file = self.cache_dir / "smart_cache_v1.2.json"
patterns_file = self.cache_dir / "access_patterns.json"
events_file = self.cache_dir / "cache_events.json"
# 保存缓存条目
with open(cache_file, 'w', encoding='utf-8') as f:
json.dump({
'version': '1.2',
'entries': [entry.to_dict() for entry in self._cache.values()]
}, f, indent=2)
# 保存访问模式
with open(patterns_file, 'w', encoding='utf-8') as f:
patterns = {
k: {'sequence': p.access_sequence, 'frequency': p.frequency}
for k, p in self._access_patterns.items()
}
json.dump(patterns, f, indent=2)
# 保存事件日志(最近的)
with open(events_file, 'w', encoding='utf-8') as f:
events = [
{'type': e.event_type.name, 'key': e.key,
'timestamp': e.timestamp, 'details': e.details}
for e in self._events[-1000:] # 只保存最近1000条
]
json.dump(events, f, indent=2)
def set_compiler(self, callback: Callable):
"""设置编译器回调(用于预热编译)"""
self._compiler_callback = callback
# ==================== 核心缓存操作 ====================
def get(self, key: str, content_hash: str = "") -> Optional[str]:
"""
获取缓存 - 缓存即编译
缓存命中 = 零成本获得编译结果
"""
if key in self._cache:
entry = self._cache[key]
# 验证内容哈希(如果提供)
if content_hash and entry.content_hash != content_hash:
self._record_event(CacheEventType.MISS, key, "hash mismatch")
self._misses += 1
return None
# 命中移到末尾LRU
self._cache.move_to_end(key)
entry.touch()
self._record_event(CacheEventType.HIT, key)
self._hits += 1
# 记录访问模式
self._record_access_pattern(key)
# 触发预测预热
if self.enable_warmup:
self._trigger_predictive_warmup(key)
return entry.output
self._record_event(CacheEventType.MISS, key)
self._misses += 1
return None
def put(self, key: str, output: str, content_hash: str,
dependencies: List[str] = None,
file_path: str = "",
line_range: Tuple[int, int] = (0, 0),
compile_time_ms: int = 0) -> bool:
"""
写入缓存 - 编译即缓存
每次编译结果自动持久化,全历史可追溯
"""
size_bytes = len(output.encode('utf-8'))
# 检查是否需要淘汰
while (len(self._cache) >= self.max_entries or
self._total_size + size_bytes > self.max_size_bytes):
if not self._evict_one():
break
# 创建或更新条目
entry = CacheEntry(
key=key,
content_hash=content_hash,
output=output,
timestamp=int(time.time() * 1000),
access_count=1,
last_access=int(time.time() * 1000),
dependencies=dependencies or [],
file_path=file_path,
line_range=line_range,
compile_time_ms=compile_time_ms,
size_bytes=size_bytes,
)
# 更新旧条目的大小
if key in self._cache:
self._total_size -= self._cache[key].size_bytes
self._cache[key] = entry
self._total_size += size_bytes
self._record_event(CacheEventType.WRITE, key,
f"size={size_bytes}, compile_time={compile_time_ms}ms")
# 记录访问模式
self._record_access_pattern(key)
return True
def invalidate(self, key: str):
"""使单个缓存失效"""
if key in self._cache:
self._total_size -= self._cache[key].size_bytes
del self._cache[key]
self._record_event(CacheEventType.EVICT, key, "manual invalidate")
def invalidate_by_dependency(self, dep_key: str):
"""使所有依赖指定键的缓存失效"""
to_invalidate = []
for key, entry in self._cache.items():
if dep_key in entry.dependencies:
to_invalidate.append(key)
for key in to_invalidate:
self.invalidate(key)
def _evict_one(self) -> bool:
"""淘汰一个条目LRU + 频率权重)"""
if not self._cache:
return False
# 计算淘汰分数(越低越优先淘汰)
# 分数 = access_count * 0.3 + recency_score * 0.7
now = int(time.time() * 1000)
min_score = float('inf')
evict_key = None
for key, entry in self._cache.items():
recency = (now - entry.last_access) / 1000 # 秒
score = entry.access_count * 0.3 - recency * 0.001
if score < min_score:
min_score = score
evict_key = key
if evict_key:
self._total_size -= self._cache[evict_key].size_bytes
del self._cache[evict_key]
self._evictions += 1
self._record_event(CacheEventType.EVICT, evict_key, "LRU eviction")
return True
return False
# ==================== 访问模式追踪 ====================
def _record_access_pattern(self, key: str):
"""记录访问模式"""
if self._last_accessed_key and self._last_accessed_key != key:
if self._last_accessed_key not in self._access_patterns:
self._access_patterns[self._last_accessed_key] = AccessPattern(
key=self._last_accessed_key
)
self._access_patterns[self._last_accessed_key].record_next(key)
self._last_accessed_key = key
# ==================== 预测缓存预热 ====================
def _start_warmup_thread(self):
"""启动后台预热线程"""
if self._warmup_thread and self._warmup_thread.is_alive():
return
self._warmup_running = True
self._warmup_thread = threading.Thread(target=self._warmup_worker, daemon=True)
self._warmup_thread.start()
def _warmup_worker(self):
"""预热工作线程"""
while self._warmup_running:
try:
# 等待预热任务
task = self._warmup_queue.get(timeout=1.0)
if task is None:
continue
key, content, content_hash = task
# 检查是否已缓存
if key in self._cache:
continue
# 执行预热编译
if self._compiler_callback:
try:
start = time.time()
output = self._compiler_callback(content)
compile_time = int((time.time() - start) * 1000)
self.put(key, output, content_hash,
compile_time_ms=compile_time)
self._warmups += 1
self._record_event(CacheEventType.WARMUP, key,
f"predictive warmup, time={compile_time}ms")
except Exception:
pass
except queue.Empty:
continue
def _trigger_predictive_warmup(self, key: str):
"""触发预测性预热"""
if key not in self._access_patterns:
return
pattern = self._access_patterns[key]
# 预热接下来可能访问的键
for next_key in pattern.access_sequence[:3]: # 最多预热3个
if next_key not in self._cache:
self._record_event(CacheEventType.PREDICT, next_key,
f"predicted from {key}")
# 这里只是标记预测,实际预热需要内容
# 真正的预热在 warmup_unit 中执行
def warmup_unit(self, key: str, content: str, content_hash: str):
"""手动添加预热任务"""
if key not in self._cache:
self._warmup_queue.put((key, content, content_hash))
def warmup_dependencies(self, keys: List[str],
content_provider: Callable[[str], Tuple[str, str]]):
"""
预热依赖链
content_provider: key -> (content, content_hash)
"""
for key in keys:
if key not in self._cache:
try:
content, content_hash = content_provider(key)
self._warmup_queue.put((key, content, content_hash))
except Exception:
pass
def stop_warmup(self):
"""停止预热线程"""
self._warmup_running = False
if self._warmup_thread:
self._warmup_thread.join(timeout=2.0)
# ==================== 事件日志 ====================
def _record_event(self, event_type: CacheEventType, key: str, details: str = ""):
"""记录缓存事件"""
event = CacheEvent(
event_type=event_type,
key=key,
timestamp=int(time.time() * 1000),
details=details
)
self._events.append(event)
# 限制事件数量
if len(self._events) > self._max_events:
self._events = self._events[-self._max_events//2:]
def get_recent_events(self, count: int = 100) -> List[dict]:
"""获取最近的事件"""
return [
{'type': e.event_type.name, 'key': e.key,
'timestamp': e.timestamp, 'details': e.details}
for e in self._events[-count:]
]
# ==================== 统计和诊断 ====================
def get_stats(self) -> Dict[str, Any]:
"""获取缓存统计"""
return {
'version': '1.2',
'entries': len(self._cache),
'total_size_mb': self._total_size / (1024 * 1024),
'max_entries': self.max_entries,
'max_size_mb': self.max_size_bytes / (1024 * 1024),
'hits': self._hits,
'misses': self._misses,
'hit_rate': self._hits / (self._hits + self._misses) if (self._hits + self._misses) > 0 else 0,
'evictions': self._evictions,
'warmups': self._warmups,
'access_patterns': len(self._access_patterns),
}
def get_hot_entries(self, count: int = 10) -> List[Dict]:
"""获取最热门的缓存条目"""
sorted_entries = sorted(
self._cache.values(),
key=lambda e: e.access_count,
reverse=True
)
return [
{'key': e.key, 'access_count': e.access_count,
'file': e.file_path, 'lines': e.line_range}
for e in sorted_entries[:count]
]
def get_predicted_next(self, key: str, count: int = 5) -> List[str]:
"""获取预测的下一个访问键"""
if key not in self._access_patterns:
return []
return self._access_patterns[key].access_sequence[:count]
def has(self, key: str) -> bool:
"""检查键是否存在"""
return key in self._cache
def clear(self):
"""清空缓存"""
self._cache.clear()
self._total_size = 0
self._access_patterns.clear()
self._events.clear()
def __del__(self):
"""析构时停止预热线程并保存"""
self.stop_warmup()
try:
self.save()
except Exception:
pass
# ==================== 工厂函数 ====================
_global_cache: Optional[SmartCache] = None
def get_smart_cache(cache_dir: str = ".sikuwa_cache") -> SmartCache:
"""获取全局智能缓存实例"""
global _global_cache
if _global_cache is None:
_global_cache = SmartCache(cache_dir)
return _global_cache
def create_smart_cache(cache_dir: str = ".sikuwa_cache",
max_entries: int = 10000,
max_size_mb: int = 500,
enable_warmup: bool = True) -> SmartCache:
"""创建新的智能缓存实例"""
return SmartCache(cache_dir, max_entries, max_size_mb, enable_warmup)

View File

@@ -0,0 +1,2 @@
# sikuwa/incremental/tests/__init__.py
"""减量编译测试包"""

View File

@@ -0,0 +1,360 @@
# sikuwa/incremental/tests/test_incremental.py
"""
减量编译系统测试
"""
import sys
import os
import tempfile
import unittest
from pathlib import Path
# 添加父目录到路径
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from incremental.core import (
IncrementalCompiler,
CompilationUnit,
Snapshot,
ChangeDetector,
CompilationCache,
UnitType,
UnitState
)
from incremental.analyzer import PythonAnalyzer, BlockType
class TestPythonAnalyzer(unittest.TestCase):
"""测试 Python 分析器"""
def setUp(self):
self.analyzer = PythonAnalyzer()
def test_analyze_function(self):
"""测试函数分析"""
code = '''
def hello(name):
"""Say hello"""
print(f"Hello, {name}!")
'''
blocks = self.analyzer.analyze(code, "test.py")
# 应该检测到函数块
func_blocks = [b for b in blocks if b.type == BlockType.FUNCTION]
self.assertEqual(len(func_blocks), 1)
self.assertEqual(func_blocks[0].name, "hello")
def test_analyze_class(self):
"""测试类分析"""
code = '''
class MyClass:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
'''
blocks = self.analyzer.analyze(code, "test.py")
# 应该检测到类块
class_blocks = [b for b in blocks if b.type == BlockType.CLASS]
self.assertEqual(len(class_blocks), 1)
self.assertEqual(class_blocks[0].name, "MyClass")
def test_analyze_import(self):
"""测试导入分析"""
code = '''
import os
from sys import path
from pathlib import Path
'''
blocks = self.analyzer.analyze(code, "test.py")
import_blocks = [b for b in blocks if b.type == BlockType.IMPORT]
self.assertEqual(len(import_blocks), 3)
def test_dependency_extraction(self):
"""测试依赖提取"""
code = '''
def outer():
def inner():
return x
return inner()
'''
blocks = self.analyzer.analyze(code, "test.py")
func_blocks = [b for b in blocks if b.type == BlockType.FUNCTION]
# outer 函数应该依赖 x
self.assertEqual(len(func_blocks), 1)
self.assertIn('x', func_blocks[0].references)
class TestChangeDetector(unittest.TestCase):
"""测试变更检测器"""
def setUp(self):
self.detector = ChangeDetector()
def test_detect_addition(self):
"""测试新增检测"""
old = Snapshot()
old.units = {}
new_unit = CompilationUnit(
id="u1", content="def foo(): pass",
start_line=1, end_line=1, file_path="test.py"
)
new_unit.compute_hash()
new = Snapshot()
new.units = {"u1": new_unit}
changes = self.detector.detect_changes(old, new)
self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].unit_id, "u1")
self.assertEqual(changes[0].change_type, UnitState.ADDED)
def test_detect_modification(self):
"""测试修改检测"""
old_unit = CompilationUnit(
id="u1", content="def foo(): pass",
start_line=1, end_line=1, file_path="test.py"
)
old_unit.compute_hash()
old = Snapshot()
old.units = {"u1": old_unit}
new_unit = CompilationUnit(
id="u1", content="def foo(): return 1",
start_line=1, end_line=1, file_path="test.py"
)
new_unit.compute_hash()
new = Snapshot()
new.units = {"u1": new_unit}
changes = self.detector.detect_changes(old, new)
self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].unit_id, "u1")
self.assertEqual(changes[0].change_type, UnitState.MODIFIED)
def test_detect_deletion(self):
"""测试删除检测"""
old_unit = CompilationUnit(
id="u1", content="def foo(): pass",
start_line=1, end_line=1, file_path="test.py"
)
old_unit.compute_hash()
old = Snapshot()
old.units = {"u1": old_unit}
new = Snapshot()
new.units = {}
changes = self.detector.detect_changes(old, new)
self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].change_type, UnitState.DELETED)
class TestCompilationCache(unittest.TestCase):
"""测试编译缓存"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.cache = CompilationCache(self.temp_dir)
def tearDown(self):
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_put_get(self):
"""测试缓存存取"""
self.cache.put("key1", "value1", "hash1")
result = self.cache.get("key1")
self.assertEqual(result, "value1")
def test_get_nonexistent(self):
"""测试获取不存在的键"""
result = self.cache.get("nonexistent")
self.assertEqual(result, "") # 返回空字符串
def test_persistence(self):
"""测试持久化"""
self.cache.put("key1", "value1", "hash1")
self.cache.save()
# 创建新缓存实例
cache2 = CompilationCache(self.temp_dir)
result = cache2.get("key1")
self.assertEqual(result, "value1")
class TestIncrementalCompiler(unittest.TestCase):
"""测试减量编译器"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.compiler = IncrementalCompiler(self.temp_dir)
# 设置简单的编译器(返回大写代码)
self.compiler.set_compiler(lambda unit: unit.content.upper())
def tearDown(self):
import shutil
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_initial_compile(self):
"""测试初始编译"""
code = '''
def hello():
print("Hello")
def world():
print("World")
'''
self.compiler.analyze_source("test.py", code)
changes = self.compiler.update_source("test.py", code)
# 首次编译,所有单元都应该是新的
self.assertGreater(len(changes), 0)
# 编译
outputs = self.compiler.compile_all_pending()
self.assertGreater(len(outputs), 0)
def test_incremental_compile(self):
"""测试增量编译"""
# 初始代码
code1 = '''
def hello():
print("Hello")
def world():
print("World")
'''
self.compiler.analyze_source("test.py", code1)
self.compiler.update_source("test.py", code1)
outputs1 = self.compiler.compile_all_pending()
# 修改一个函数
code2 = '''
def hello():
print("Hello Modified")
def world():
print("World")
'''
changes = self.compiler.update_source("test.py", code2)
# 应该有变更
self.assertGreater(len(changes), 0)
# 再次编译
outputs2 = self.compiler.compile_all_pending()
# 验证有输出
self.assertGreater(len(outputs1) + len(outputs2), 0)
def test_dependency_propagation(self):
"""测试依赖传播"""
code = '''
x = 10
def get_x():
return x
def double_x():
return get_x() * 2
'''
self.compiler.analyze_source("test.py", code)
self.compiler.update_source("test.py", code)
self.compiler.compile_all_pending()
# 修改 x 的值
code2 = '''
x = 20
def get_x():
return x
def double_x():
return get_x() * 2
'''
changes = self.compiler.update_source("test.py", code2)
# 应该检测到变更x 变了,依赖它的也应该被标记)
self.assertGreater(len(changes), 0)
def test_combined_output(self):
"""测试合并输出"""
code = '''
import os
def hello():
print("Hello")
def world():
print("World")
'''
self.compiler.analyze_source("test.py", code)
self.compiler.update_source("test.py", code)
self.compiler.compile_all_pending()
combined = self.compiler.get_combined_output("test.py")
# 合并输出应该包含所有编译产物
self.assertGreater(len(combined), 0)
class TestBlockBoundary(unittest.TestCase):
"""测试边界触发器"""
def setUp(self):
self.analyzer = PythonAnalyzer()
def test_class_contains_methods(self):
"""测试类包含其方法"""
code = '''
class MyClass:
def method1(self):
pass
def method2(self):
pass
'''
blocks = self.analyzer.analyze(code, "test.py")
class_blocks = [b for b in blocks if b.type == BlockType.CLASS]
self.assertEqual(len(class_blocks), 1)
# 类块应该包含整个类定义
class_block = class_blocks[0]
self.assertIn("method1", class_block.content)
self.assertIn("method2", class_block.content)
def run_tests():
"""运行所有测试"""
loader = unittest.TestLoader()
suite = unittest.TestSuite()
suite.addTests(loader.loadTestsFromTestCase(TestPythonAnalyzer))
suite.addTests(loader.loadTestsFromTestCase(TestChangeDetector))
suite.addTests(loader.loadTestsFromTestCase(TestCompilationCache))
suite.addTests(loader.loadTestsFromTestCase(TestIncrementalCompiler))
suite.addTests(loader.loadTestsFromTestCase(TestBlockBoundary))
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
return result.wasSuccessful()
if __name__ == '__main__':
success = run_tests()
sys.exit(0 if success else 1)

491
log.py Normal file
View File

@@ -0,0 +1,491 @@
# sikuwa/log.py
"""
Sikuwa 超详细日志系统
支持 34 级日志等级,用于精确追踪程序执行
"""
import logging
import sys
import time
import functools
from pathlib import Path
from typing import Optional, Any, Callable
from datetime import datetime
from enum import IntEnum
# 兼容扁平结构和包结构的导入
try:
from sikuwa.i18n import _
except ImportError:
from i18n import _
class LogLevel(IntEnum):
"""34 级日志等级"""
# TRACE 级别 (1-5)
TRACE_IO = 1 # 极细粒度 I/O 跟踪
TRACE_STATE = 2 # 极细粒度状态变更
TRACE_PERF = 3 # 微观性能计时
TRACE_FLOW = 4 # 函数进入退出跟踪
TRACE_MSG = 5 # 消息队列/事件传递
# DEBUG 级别 (6-10)
DEBUG_DETAIL = 6 # 详细调试信息
DEBUG_CONFIG = 7 # 配置/启动参数
DEBUG_CONN = 8 # 连接建立/断开
DEBUG_CACHE = 9 # 缓存命中/失效
DEBUG_SQL = 10 # SQL/查询执行
# INFO 级别 (11-15)
INFO_OPERATION = 11 # 业务操作记录
INFO_USER = 12 # 用户可见操作
INFO_METRIC = 13 # 周期性指标快照
INFO_DEPLOY = 14 # 部署/升级事件
INFO_HEALTH = 15 # 健康检查通过
# NOTICE 级别 (16-18)
NOTICE_CONFIG = 16 # 非致命配置变更
NOTICE_POLICY = 17 # 策略/权限变更
NOTICE_THRESHOLD = 18 # 接近阈值
# WARN 级别 (19-23)
WARN_MINOR = 19 # 轻微异常
WARN_RETRY = 20 # 重试事件
WARN_RESOURCE = 21 # 资源接近上限
WARN_DEPRECATED = 22 # 使用不推荐接口
WARN_SECURITY = 23 # 可疑安全事件
# ERROR 级别 (24-28)
ERROR_MINIMAL = 24 # 业务错误
ERROR_DB = 25 # 数据库错误
ERROR_INTEGRITY = 26 # 数据一致性问题
ERROR_DEPENDENCY = 27 # 外部依赖失败
ERROR_SECURITY = 28 # 已确认安全问题
# CRITICAL 级别 (29-31)
CRITICAL_SERVICE = 29 # 服务功能不可用
CRITICAL_PERSIST = 30 # 数据持久化失败
CRITICAL_DEGRADED = 31 # 系统降级
# FATAL 级别 (32-33)
FATAL_NODE = 32 # 节点宕机/崩溃
FATAL_CASCADE = 33 # 级联故障
# EMERGENCY 级别 (34)
EMERGENCY_SECURITY = 34 # 严重安全事件
class ColorFormatter(logging.Formatter):
"""带颜色的日志格式化器"""
# ANSI 颜色代码
COLORS = {
'TRACE': '\033[90m', # 灰色
'DEBUG': '\033[36m', # 青色
'INFO': '\033[32m', # 绿色
'NOTICE': '\033[94m', # 蓝色
'WARNING': '\033[33m', # 黄色
'ERROR': '\033[31m', # 红色
'CRITICAL': '\033[35m', # 紫色
'FATAL': '\033[91m', # 亮红色
'EMERGENCY': '\033[97;41m', # 白底红字
'RESET': '\033[0m'
}
def formatTime(self, record, datefmt=None):
"""自定义时间格式化,支持毫秒"""
ct = self.converter(record.created)
if datefmt:
# 标准时间格式化(不使用 %f
s = time.strftime(datefmt, ct)
# 手动添加毫秒
msecs = int((record.created - int(record.created)) * 1000)
s = f"{s}.{msecs:03d}"
else:
# 默认格式
s = time.strftime("%Y-%m-%d %H:%M:%S", ct)
msecs = int((record.created - int(record.created)) * 1000)
s = f"{s}.{msecs:03d}"
return s
def format(self, record):
# 获取日志级别颜色
level_name = record.levelname
color = self.COLORS.get(level_name.split('_')[0], self.COLORS['RESET'])
# 格式化消息
log_message = super().format(record)
# 添加颜色
return f"{color}{log_message}{self.COLORS['RESET']}"
class SikuwaLogger:
"""Sikuwa 超详细日志器"""
def __init__(self, name: str, log_dir: Optional[Path] = None, level: int = LogLevel.TRACE_FLOW):
self.name = name
self.logger = logging.getLogger(name)
self.logger.setLevel(1) # 设置为最低级别,让所有消息都能通过
self.logger.propagate = False
# 创建日志目录
if log_dir is None:
log_dir = Path.cwd() / "sikuwa_logs"
log_dir.mkdir(parents=True, exist_ok=True)
# 控制台处理器(彩色输出)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(level)
console_formatter = ColorFormatter(
'%(asctime)s [%(levelname)-18s] %(name)s:%(funcName)s:%(lineno)d - %(message)s',
datefmt='%H:%M:%S' # 移除 %f在 formatTime 中手动添加毫秒
)
console_handler.setFormatter(console_formatter)
self.logger.addHandler(console_handler)
# 文件处理器(完整日志)
timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
file_handler = logging.FileHandler(
log_dir / f"sikuwa-detailed-{timestamp}.log",
encoding='utf-8'
)
file_handler.setLevel(1)
file_formatter = ColorFormatter(
'%(asctime)s [%(levelname)-18s] %(name)s:%(funcName)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S' # 移除 %f在 formatTime 中手动添加毫秒
)
file_handler.setFormatter(file_formatter)
self.logger.addHandler(file_handler)
# 注册自定义级别
self._register_custom_levels()
def _register_custom_levels(self):
"""注册所有自定义日志级别"""
for level in LogLevel:
level_name = level.name
if not hasattr(logging, level_name):
logging.addLevelName(level.value, level_name)
# === TRACE 级别快捷方法 ===
def trace_io(self, msg: str, *args, **kwargs):
"""极细粒度 I/O 跟踪"""
self.logger.log(LogLevel.TRACE_IO, msg, *args, **kwargs)
def trace_state(self, msg: str, *args, **kwargs):
"""极细粒度状态变更"""
self.logger.log(LogLevel.TRACE_STATE, msg, *args, **kwargs)
def trace_perf(self, msg: str, *args, **kwargs):
"""微观性能计时"""
self.logger.log(LogLevel.TRACE_PERF, msg, *args, **kwargs)
def trace_flow(self, msg: str, *args, **kwargs):
"""函数进入退出跟踪"""
self.logger.log(LogLevel.TRACE_FLOW, msg, *args, **kwargs)
def trace_msg(self, msg: str, *args, **kwargs):
"""消息队列/事件传递"""
self.logger.log(LogLevel.TRACE_MSG, msg, *args, **kwargs)
# === DEBUG 级别快捷方法 ===
def debug_detail(self, msg: str, *args, **kwargs):
"""详细调试信息"""
self.logger.log(LogLevel.DEBUG_DETAIL, msg, *args, **kwargs)
def debug_config(self, msg: str, *args, **kwargs):
"""配置/启动参数"""
self.logger.log(LogLevel.DEBUG_CONFIG, msg, *args, **kwargs)
def debug_conn(self, msg: str, *args, **kwargs):
"""连接建立/断开"""
self.logger.log(LogLevel.DEBUG_CONN, msg, *args, **kwargs)
def debug_cache(self, msg: str, *args, **kwargs):
"""缓存命中/失效"""
self.logger.log(LogLevel.DEBUG_CACHE, msg, *args, **kwargs)
def debug_sql(self, msg: str, *args, **kwargs):
"""SQL/查询执行"""
self.logger.log(LogLevel.DEBUG_SQL, msg, *args, **kwargs)
# === INFO 级别快捷方法 ===
def info_operation(self, msg: str, *args, **kwargs):
"""业务操作记录"""
self.logger.log(LogLevel.INFO_OPERATION, msg, *args, **kwargs)
def info_user(self, msg: str, *args, **kwargs):
"""用户可见操作"""
self.logger.log(LogLevel.INFO_USER, msg, *args, **kwargs)
def info_metric(self, msg: str, *args, **kwargs):
"""周期性指标快照"""
self.logger.log(LogLevel.INFO_METRIC, msg, *args, **kwargs)
def info_deploy(self, msg: str, *args, **kwargs):
"""部署/升级事件"""
self.logger.log(LogLevel.INFO_DEPLOY, msg, *args, **kwargs)
def info_health(self, msg: str, *args, **kwargs):
"""健康检查通过"""
self.logger.log(LogLevel.INFO_HEALTH, msg, *args, **kwargs)
# === NOTICE 级别快捷方法 ===
def notice_config(self, msg: str, *args, **kwargs):
"""非致命配置变更"""
self.logger.log(LogLevel.NOTICE_CONFIG, msg, *args, **kwargs)
def notice_policy(self, msg: str, *args, **kwargs):
"""策略/权限变更"""
self.logger.log(LogLevel.NOTICE_POLICY, msg, *args, **kwargs)
def notice_threshold(self, msg: str, *args, **kwargs):
"""接近阈值"""
self.logger.log(LogLevel.NOTICE_THRESHOLD, msg, *args, **kwargs)
# === WARN 级别快捷方法 ===
def warn_minor(self, msg: str, *args, **kwargs):
"""轻微异常"""
self.logger.log(LogLevel.WARN_MINOR, msg, *args, **kwargs)
def warn_retry(self, msg: str, *args, **kwargs):
"""重试事件"""
self.logger.log(LogLevel.WARN_RETRY, msg, *args, **kwargs)
def warn_resource(self, msg: str, *args, **kwargs):
"""资源接近上限"""
self.logger.log(LogLevel.WARN_RESOURCE, msg, *args, **kwargs)
def warn_deprecated(self, msg: str, *args, **kwargs):
"""使用不推荐接口"""
self.logger.log(LogLevel.WARN_DEPRECATED, msg, *args, **kwargs)
def warn_security(self, msg: str, *args, **kwargs):
"""可疑安全事件"""
self.logger.log(LogLevel.WARN_SECURITY, msg, *args, **kwargs)
# === ERROR 级别快捷方法 ===
def error_minimal(self, msg: str, *args, **kwargs):
"""业务错误"""
self.logger.log(LogLevel.ERROR_MINIMAL, msg, *args, **kwargs)
def error_db(self, msg: str, *args, **kwargs):
"""数据库错误"""
self.logger.log(LogLevel.ERROR_DB, msg, *args, **kwargs)
def error_integrity(self, msg: str, *args, **kwargs):
"""数据一致性问题"""
self.logger.log(LogLevel.ERROR_INTEGRITY, msg, *args, **kwargs)
def error_dependency(self, msg: str, *args, **kwargs):
"""外部依赖失败"""
self.logger.log(LogLevel.ERROR_DEPENDENCY, msg, *args, **kwargs)
def error_security(self, msg: str, *args, **kwargs):
"""已确认安全问题"""
self.logger.log(LogLevel.ERROR_SECURITY, msg, *args, **kwargs)
# === CRITICAL 级别快捷方法 ===
def critical_service(self, msg: str, *args, **kwargs):
"""服务功能不可用"""
self.logger.log(LogLevel.CRITICAL_SERVICE, msg, *args, **kwargs)
def critical_persist(self, msg: str, *args, **kwargs):
"""数据持久化失败"""
self.logger.log(LogLevel.CRITICAL_PERSIST, msg, *args, **kwargs)
def critical_degraded(self, msg: str, *args, **kwargs):
"""系统降级"""
self.logger.log(LogLevel.CRITICAL_DEGRADED, msg, *args, **kwargs)
# === FATAL 级别快捷方法 ===
def fatal_node(self, msg: str, *args, **kwargs):
"""节点宕机/崩溃"""
self.logger.log(LogLevel.FATAL_NODE, msg, *args, **kwargs)
def fatal_cascade(self, msg: str, *args, **kwargs):
"""级联故障"""
self.logger.log(LogLevel.FATAL_CASCADE, msg, *args, **kwargs)
# === EMERGENCY 级别快捷方法 ===
def emergency_security(self, msg: str, *args, **kwargs):
"""严重安全事件"""
self.logger.log(LogLevel.EMERGENCY_SECURITY, msg, *args, **kwargs)
# === 装饰器:自动追踪函数执行 ===
def trace_function(self, func: Callable) -> Callable:
"""装饰器:追踪函数执行"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
func_name = func.__name__
self.trace_flow(f">>> 进入函数: {func_name}")
self.trace_flow(f" 参数: args={args}, kwargs={kwargs}")
start_time = time.perf_counter()
try:
result = func(*args, **kwargs)
elapsed = (time.perf_counter() - start_time) * 1000
self.trace_perf(f" 函数 {func_name} 耗时: {elapsed:.3f}ms")
self.trace_flow(f"<<< 退出函数: {func_name}, 返回值: {result}")
return result
except Exception as e:
elapsed = (time.perf_counter() - start_time) * 1000
self.error_minimal(f"!!! 函数 {func_name} 异常 (耗时 {elapsed:.3f}ms): {e}")
raise
return wrapper
def trace_method(self, func: Callable) -> Callable:
"""装饰器:追踪类方法执行"""
@functools.wraps(func)
def wrapper(self_obj, *args, **kwargs):
class_name = self_obj.__class__.__name__
func_name = func.__name__
self.trace_flow(f">>> 进入方法: {class_name}.{func_name}")
self.trace_flow(f" 参数: args={args}, kwargs={kwargs}")
start_time = time.perf_counter()
try:
result = func(self_obj, *args, **kwargs)
elapsed = (time.perf_counter() - start_time) * 1000
self.trace_perf(f" 方法 {class_name}.{func_name} 耗时: {elapsed:.3f}ms")
self.trace_flow(f"<<< 退出方法: {class_name}.{func_name}, 返回值类型: {type(result).__name__}")
return result
except Exception as e:
elapsed = (time.perf_counter() - start_time) * 1000
self.error_minimal(f"!!! 方法 {class_name}.{func_name} 异常 (耗时 {elapsed:.3f}ms): {e}")
raise
return wrapper
# === 全局日志器实例 ===
_global_logger: Optional[SikuwaLogger] = None
def get_logger(name: str = "sikuwa", level: int = LogLevel.TRACE_FLOW) -> SikuwaLogger:
"""获取全局日志器实例"""
global _global_logger
if _global_logger is None:
_global_logger = SikuwaLogger(name, level=level)
return _global_logger
def set_log_level(level: int):
"""设置日志级别"""
logger = get_logger()
for handler in logger.logger.handlers:
if isinstance(handler, logging.StreamHandler) and handler.stream == sys.stdout:
handler.setLevel(level)
# === 便捷函数 ===
def trace_io(msg: str, *args, **kwargs):
"""极细粒度 I/O 跟踪"""
get_logger().trace_io(msg, *args, **kwargs)
def trace_state(msg: str, *args, **kwargs):
"""极细粒度状态变更"""
get_logger().trace_state(msg, *args, **kwargs)
def trace_perf(msg: str, *args, **kwargs):
"""微观性能计时"""
get_logger().trace_perf(msg, *args, **kwargs)
def trace_flow(msg: str, *args, **kwargs):
"""函数进入退出跟踪"""
get_logger().trace_flow(msg, *args, **kwargs)
def debug_detail(msg: str, *args, **kwargs):
"""详细调试信息"""
get_logger().debug_detail(msg, *args, **kwargs)
def debug_config(msg: str, *args, **kwargs):
"""配置/启动参数"""
get_logger().debug_config(msg, *args, **kwargs)
def info_operation(msg: str, *args, **kwargs):
"""业务操作记录"""
get_logger().info_operation(msg, *args, **kwargs)
def warn_minor(msg: str, *args, **kwargs):
"""轻微异常"""
get_logger().warn_minor(msg, *args, **kwargs)
def error_minimal(msg: str, *args, **kwargs):
"""业务错误"""
get_logger().error_minimal(msg, *args, **kwargs)
def critical_service(msg: str, *args, **kwargs):
"""服务功能不可用"""
get_logger().critical_service(msg, *args, **kwargs)
# === 上下文管理器:性能计时 ===
class PerfTimer:
"""性能计时上下文管理器"""
def __init__(self, name: str, logger: Optional[SikuwaLogger] = None):
self.name = name
self.logger = logger or get_logger()
self.start_time = None
self.end_time = None
def __enter__(self):
self.logger.trace_perf(f" 开始计时: {self.name}")
self.start_time = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.end_time = time.perf_counter()
elapsed = (self.end_time - self.start_time) * 1000
if exc_type is None:
self.logger.trace_perf(f" 完成计时: {self.name}, 耗时 {elapsed:.3f}ms")
else:
self.logger.trace_perf(f" 异常计时: {self.name}, 耗时 {elapsed:.3f}ms, 异常: {exc_val}")
return False # 不抑制异常
# === 使用示例 ===
if __name__ == '__main__':
# 创建日志器
logger = get_logger("test", level=LogLevel.TRACE_IO)
# 测试所有级别
logger.trace_io(_("这是 TRACE_IO 级别日志"))
logger.trace_state(_("这是 TRACE_STATE 级别日志"))
logger.trace_perf(_("这是 TRACE_PERF 级别日志"))
logger.trace_flow(_("这是 TRACE_FLOW 级别日志"))
logger.debug_detail(_("这是 DEBUG_DETAIL 级别日志"))
logger.debug_config(_("这是 DEBUG_CONFIG 级别日志"))
logger.info_operation(_("这是 INFO_OPERATION 级别日志"))
logger.warn_minor(_("这是 WARN_MINOR 级别日志"))
logger.error_minimal(_("这是 ERROR_MINIMAL 级别日志"))
logger.critical_service(_("这是 CRITICAL_SERVICE 级别日志"))
# 测试装饰器
@logger.trace_function
def test_function(x, y):
time.sleep(0.1)
return x + y
result = test_function(1, 2)
# 测试性能计时
with PerfTimer(_("测试计时块"), logger):
time.sleep(0.05)
print(_("执行中..."))
print(f"\n{_('所有测试完成!')}")

68
nuitka_loader.py Normal file
View File

@@ -0,0 +1,68 @@
"""
Nuitka 动态加载器
在运行时将打包的 Nuitka 副本加载到 sys.path
"""
import sys
from pathlib import Path
class NuitkaLoader:
"""管理打包的 Nuitka 副本"""
@staticmethod
def get_bundled_path() -> Path:
"""获取打包的 Nuitka 路径"""
if getattr(sys, 'frozen', False):
# 运行在打包后的 exe 中
if hasattr(sys, '_MEIPASS'):
# PyInstaller/Nuitka 打包
base = Path(sys._MEIPASS)
else:
base = Path(sys.executable).parent
else:
# 开发模式
base = Path(__file__).parent.parent / ".venv" / "Lib" / "site-packages"
return base / "bundled_packages"
@staticmethod
def load_nuitka():
"""加载打包的 Nuitka 到 sys.path"""
bundled_path = NuitkaLoader.get_bundled_path()
if bundled_path.exists():
# 将打包的 packages 目录添加到 sys.path 最前面
bundled_str = str(bundled_path)
if bundled_str not in sys.path:
sys.path.insert(0, bundled_str)
print(f"✓ 已加载打包的 Nuitka: {bundled_path}")
return True
# 回退:尝试使用系统已安装的 Nuitka
try:
import nuitka
print(f"✓ 使用系统 Nuitka: {nuitka.__file__}")
return True
except ImportError:
print("✗ 找不到 Nuitka")
return False
@staticmethod
def ensure_nuitka():
"""确保 Nuitka 可用"""
# 首先尝试加载打包的版本
if NuitkaLoader.load_nuitka():
return True
# 如果都失败,提示用户安装
print("=" * 70)
print("❌ Nuitka 未找到!")
print("\n请安装 Nuitka:")
print(" pip install nuitka ordered-set zstandard")
print("=" * 70)
sys.exit(1)
# 模块导入时自动加载
NuitkaLoader.ensure_nuitka()

317
parser.py Normal file
View File

@@ -0,0 +1,317 @@
# sikuwa/parser.py
"""
Sikuwa 配置解析器
"""
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
@dataclass
class NuitkaConfig:
"""Nuitka 专属配置"""
standalone: bool = True
follow_imports: bool = True
remove_output: bool = True
show_progress: bool = True
include_packages: List[str] = field(default_factory=list)
include_modules: List[str] = field(default_factory=list)
exclude_modules: List[str] = field(default_factory=list)
windows_icon: Optional[str] = None
windows_company: Optional[str] = None
windows_product: Optional[str] = None
macos_app_name: Optional[str] = None
@dataclass
class BuildConfig:
"""构建配置(完整版)"""
# 基础配置
project_name: str
main_script: str
version: str = "1.0.0"
# 目录配置
src_dir: str = "."
output_dir: str = "dist"
build_dir: str = "build"
# 平台配置
platforms: List[str] = field(default_factory=lambda: ["windows", "linux"])
# Nuitka 配置
nuitka: NuitkaConfig = field(default_factory=NuitkaConfig)
# 资源配置
resources: List[Dict[str, str]] = field(default_factory=list)
# 构建钩子
pre_build_commands: List[str] = field(default_factory=list)
post_build_commands: List[str] = field(default_factory=list)
# 向后兼容属性
@property
def name(self) -> str:
return self.project_name
@property
def entry(self) -> str:
return self.main_script
# 兼容旧版属性
icon: Optional[str] = None
console: bool = False
onefile: bool = True
strip: bool = True
upx: bool = False
follow_imports: bool = True
standalone: bool = True
include_data: List[tuple] = field(default_factory=list)
include_modules: List[str] = field(default_factory=list)
exclude_modules: List[str] = field(default_factory=list)
product_name: Optional[str] = None
product_version: str = "1.0.0"
company_name: Optional[str] = None
file_description: Optional[str] = None
extra_args: List[str] = field(default_factory=list)
class ConfigParser:
"""配置文件解析器"""
def __init__(self, config_path: Path):
self.config_path = config_path
self.config: Dict[str, Any] = {}
def parse(self) -> BuildConfig:
"""解析配置文件"""
if not self.config_path.exists():
raise FileNotFoundError(f"配置文件不存在: {self.config_path}")
file_content = self.config_path.read_text(encoding='utf-8')
if self.config_path.suffix == '.toml':
self.config = self._parse_toml(file_content)
elif self.config_path.suffix in ['.yaml', '.yml']:
self.config = self._parse_yaml(file_content)
elif self.config_path.suffix == '.json':
self.config = self._parse_json(file_content)
else:
raise ValueError(f"不支持的配置文件格式: {self.config_path.suffix}")
return self._to_build_config()
def _parse_toml(self, file_content: str) -> Dict:
"""解析 TOML 配置"""
try:
import tomllib
except ImportError:
try:
import tomli as tomllib
except ImportError:
raise ImportError("需要安装 tomli: pip install tomli")
return tomllib.loads(file_content)
def _parse_yaml(self, file_content: str) -> Dict:
"""解析 YAML 配置"""
try:
import yaml
return yaml.safe_load(file_content)
except ImportError:
raise ImportError("需要安装 PyYAML: pip install pyyaml")
def _parse_json(self, file_content: str) -> Dict:
"""解析 JSON 配置"""
import json
return json.loads(file_content)
def _to_build_config(self) -> BuildConfig:
"""转换为 BuildConfig 对象"""
build_section = self.config.get('build', {})
config_name = build_section.get('name')
config_entry = build_section.get('entry')
if not config_name:
raise ValueError("配置文件缺少必需字段: build.name")
if not config_entry:
raise ValueError("配置文件缺少必需字段: build.entry")
# 创建 Nuitka 配置
nuitka_section = build_section.get('nuitka', {})
nuitka_config = NuitkaConfig(
standalone=nuitka_section.get('standalone', True),
follow_imports=nuitka_section.get('follow_imports', True),
remove_output=nuitka_section.get('remove_output', True),
show_progress=nuitka_section.get('show_progress', True),
include_packages=nuitka_section.get('include_packages', []),
include_modules=nuitka_section.get('include_modules', []),
exclude_modules=nuitka_section.get('exclude_modules', []),
windows_icon=build_section.get('icon'),
windows_company=build_section.get('company_name'),
windows_product=build_section.get('product_name', config_name),
macos_app_name=nuitka_section.get('macos_app_name')
)
# 处理资源文件
resources = []
data_files = nuitka_section.get('include_data', [])
if data_files:
for item in data_files:
if isinstance(item, dict):
resources.append({
'src': item.get('src', ''),
'dest': item.get('dst', item.get('dest', ''))
})
# 获取产品信息
product_section = self.config.get('product', {})
# 创建完整配置
result = BuildConfig(
project_name=config_name,
main_script=config_entry,
version=product_section.get('version', '1.0.0'),
src_dir=build_section.get('src_dir', '.'),
output_dir=build_section.get('output_dir', 'dist'),
build_dir=build_section.get('build_dir', 'build'),
platforms=build_section.get('platforms', ['windows', 'linux']),
nuitka=nuitka_config,
resources=resources,
pre_build_commands=build_section.get('pre_build_commands', []),
post_build_commands=build_section.get('post_build_commands', []),
# 兼容旧版属性
icon=build_section.get('icon'),
console=build_section.get('console', False),
onefile=build_section.get('onefile', True),
strip=build_section.get('strip', True),
upx=build_section.get('upx', False),
follow_imports=nuitka_section.get('follow_imports', True),
standalone=nuitka_section.get('standalone', True),
include_modules=nuitka_section.get('include_modules', []),
exclude_modules=nuitka_section.get('exclude_modules', []),
product_name=product_section.get('name', config_name),
product_version=product_section.get('version', '1.0.0'),
company_name=product_section.get('company'),
file_description=product_section.get('description'),
extra_args=build_section.get('extra_args', [])
)
# 处理 include_data 兼容性
if data_files:
result.include_data = [
(item['src'], item.get('dst', item.get('dest', '')))
for item in data_files if isinstance(item, dict)
]
return result
def parse_config(config_path: Path) -> BuildConfig:
"""快速解析配置文件"""
parser = ConfigParser(config_path)
return parser.parse()
def create_default_config(output_path: Path, format_type: str = 'toml'):
"""创建默认配置文件"""
if format_type == 'toml':
template_content = '''[build]
name = "myapp"
entry = "main.py"
src_dir = "."
output_dir = "dist"
build_dir = "build"
platforms = ["windows", "linux"]
console = false
onefile = true
[build.nuitka]
standalone = true
follow_imports = true
remove_output = true
show_progress = true
include_packages = []
include_modules = []
exclude_modules = []
[product]
name = "My Application"
version = "1.0.0"
company = "Your Company"
description = "Application description"
'''
elif format_type == 'yaml':
template_content = '''build:
name: myapp
entry: main.py
src_dir: "."
output_dir: dist
build_dir: build
platforms: [windows, linux]
console: false
onefile: true
nuitka:
standalone: true
follow_imports: true
remove_output: true
show_progress: true
product:
name: My Application
version: 1.0.0
company: Your Company
description: Application description
'''
elif format_type == 'json':
template_content = '''{
"build": {
"name": "myapp",
"entry": "main.py",
"src_dir": ".",
"output_dir": "dist",
"build_dir": "build",
"platforms": ["windows", "linux"],
"console": false,
"onefile": true,
"nuitka": {
"standalone": true,
"follow_imports": true,
"remove_output": true,
"show_progress": true
}
},
"product": {
"name": "My Application",
"version": "1.0.0",
"company": "Your Company",
"description": "Application description"
}
}'''
else:
raise ValueError(f"不支持的格式: {format_type}")
output_path.write_text(template_content.strip(), encoding='utf-8')
def validate_config(config: BuildConfig) -> List[str]:
"""验证配置有效性"""
errors = []
# 检查源码目录
src_dir = Path(config.src_dir)
if not src_dir.exists():
errors.append(f"源码目录不存在: {config.src_dir}")
# 检查入口文件
entry_file = src_dir / config.main_script
if not entry_file.exists():
errors.append(f"入口文件不存在: {entry_file}")
# 检查图标文件
if config.icon:
icon_file = Path(config.icon)
if not icon_file.exists():
errors.append(f"图标文件不存在: {config.icon}")
return errors

47
sikuwa.toml Normal file
View File

@@ -0,0 +1,47 @@
[sikuwa]
project_name = "sikuwa"
version = "1.3.0"
main_script = "sikuwa/__main__.py"
src_dir = "."
output_dir = "dist"
platforms = ["windows"]
[sikuwa.nuitka]
standalone = true
onefile = false # 推荐目录模式,方便调试
# 只包含 Sikuwa 运行时需要的包
include_packages = [
"click",
"tomli_w",
]
# 关键:将整个 Nuitka 包作为数据文件复制(不作为代码导入)
include_data_dirs = [
# 复制整个 nuitka 包到 bundled_packages/
{ src = ".venv/Lib/site-packages/nuitka", dest = "bundled_packages/nuitka" },
{ src = ".venv/Lib/site-packages/nuitka-*.dist-info", dest = "bundled_packages/nuitka.dist-info" },
# 复制 Nuitka 的依赖
{ src = ".venv/Lib/site-packages/ordered_set", dest = "bundled_packages/ordered_set" },
{ src = ".venv/Lib/site-packages/ordered_set-*.dist-info", dest = "bundled_packages/ordered_set.dist-info" },
{ src = ".venv/Lib/site-packages/zstandard", dest = "bundled_packages/zstandard" },
{ src = ".venv/Lib/site-packages/zstandard-*.dist-info", dest = "bundled_packages/zstandard.dist-info" },
]
extra_args = [
# 关键:不要导入 nuitka避免循环依赖
"--nofollow-import-to=nuitka",
"--nofollow-import-to=ordered_set",
"--nofollow-import-to=zstandard",
# 排除不必要的模块
"--nofollow-import-to=*.tests",
"--nofollow-import-to=pytest",
"--nofollow-import-to=setuptools",
"--assume-yes-for-downloads",
"--show-progress",
]

100
sikuwa_native_example.toml Normal file
View File

@@ -0,0 +1,100 @@
# Sikuwa 原生编译配置示例
# 编译流程: Python源码 → C/C++源码 → GCC/G++编译 → dll/so + exe
# 生成通用动态链接库,不使用 Python 专用格式 (.pyd)
[sikuwa]
project_name = "my_project"
version = "1.0.0"
main_script = "main.py"
src_dir = "."
output_dir = "dist"
build_dir = "build"
platforms = ["windows"]
# 编译模式: "nuitka" | "native"
compiler_mode = "native"
# 原生编译器配置
[sikuwa.native]
# 编译模式: native | cython | cffi
mode = "native"
# 编译器选择 (自动检测如果不指定)
cc = "gcc"
cxx = "g++"
# C 编译选项
c_flags = ["-O2", "-fPIC", "-Wall"]
# C++ 编译选项
cxx_flags = ["-O2", "-fPIC", "-std=c++17", "-Wall"]
# 链接选项
link_flags = []
# 输出选项
output_dll = true # 生成 dll/so 动态链接库
output_exe = true # 生成 exe 可执行文件
output_static = false # 生成静态库 (.a/.lib)
# Python 嵌入选项
embed_python = true # 嵌入 Python 解释器
python_static = false # 静态链接 Python (需要静态编译的 Python)
# 优化选项
lto = false # Link Time Optimization (增加编译时间,减小体积)
strip = true # 剥离调试符号 (减小文件体积)
# 调试选项
debug = false # 调试模式 (保留调试信息,禁用优化)
keep_c_source = false # 保留生成的 C/C++ 源码
# ========================================
# 示例2: 使用 Cython 优化的配置
# ========================================
# [sikuwa.native]
# mode = "cython" # 使用 Cython 进行 Python → C 转换
# cc = "gcc"
# cxx = "g++"
# c_flags = ["-O3", "-fPIC", "-march=native"] # 更激进的优化
# lto = true
# strip = true
# ========================================
# 示例3: 调试模式配置
# ========================================
# [sikuwa.native]
# mode = "native"
# cc = "gcc"
# cxx = "g++"
# c_flags = ["-g", "-O0", "-fPIC", "-Wall", "-Wextra"]
# debug = true
# strip = false
# keep_c_source = true # 保留 C 源码方便调试
# ========================================
# 示例4: Windows MSVC 配置
# ========================================
# [sikuwa.native]
# mode = "native"
# cc = "cl"
# cxx = "cl"
# c_flags = ["/O2", "/W3"]
# cxx_flags = ["/O2", "/W3", "/std:c++17"]
# link_flags = ["/MACHINE:X64"]
# ========================================
# 示例5: Linux 发布版配置
# ========================================
# [sikuwa.native]
# mode = "cython"
# cc = "gcc"
# cxx = "g++"
# c_flags = ["-O3", "-fPIC", "-march=x86-64", "-mtune=generic"]
# lto = true
# strip = true
# python_static = true # 静态链接 Python无需目标系统安装 Python