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
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:
44
.gitee/pipelines/ci.yml
Normal file
44
.gitee/pipelines/ci.yml
Normal 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
32
.github/CODEOWNERS
vendored
Normal 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
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: Bug 报告
|
||||
about: 报告问题以帮助改进项目
|
||||
title: '[BUG] '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 环境信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|:---|:---|
|
||||
| 操作系统 | |
|
||||
| Python 版本 | |
|
||||
| Sikuwa 版本 | |
|
||||
| Nuitka 版本 | |
|
||||
|
||||
## 问题描述
|
||||
|
||||
### 预期行为
|
||||
|
||||
清晰描述预期的行为。
|
||||
|
||||
### 实际行为
|
||||
|
||||
清晰描述实际发生的行为。
|
||||
|
||||
## 复现步骤
|
||||
|
||||
1. 执行命令 '...'
|
||||
2. 配置 '...'
|
||||
3. 查看 '...'
|
||||
4. 出现错误
|
||||
|
||||
## 配置文件
|
||||
|
||||
如适用,请提供 `sikuwa.toml` 配置文件内容:
|
||||
|
||||
```toml
|
||||
# 粘贴配置文件内容
|
||||
```
|
||||
|
||||
## 日志信息
|
||||
|
||||
```
|
||||
# 粘贴相关日志或错误信息
|
||||
```
|
||||
|
||||
## 截图
|
||||
|
||||
如适用,添加截图以帮助说明问题。
|
||||
|
||||
## 其他信息
|
||||
|
||||
任何其他相关的上下文信息。
|
||||
|
||||
## 检查清单
|
||||
|
||||
- [ ] 已搜索现有 Issue,确认问题未被报告
|
||||
- [ ] 已使用最新版本测试
|
||||
- [ ] 已提供完整的复现步骤
|
||||
51
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
51
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: 功能请求
|
||||
about: 提出新功能或改进建议
|
||||
title: '[FEATURE] '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 功能描述
|
||||
|
||||
清晰简洁地描述所需的功能。
|
||||
|
||||
## 使用场景
|
||||
|
||||
描述此功能将解决什么问题或满足什么需求。
|
||||
|
||||
## 期望方案
|
||||
|
||||
描述期望的解决方案或实现方式。
|
||||
|
||||
## 备选方案
|
||||
|
||||
描述考虑过的其他替代方案。
|
||||
|
||||
## 实现建议
|
||||
|
||||
如有技术实现建议,请在此描述:
|
||||
|
||||
```python
|
||||
# 示例代码或伪代码
|
||||
```
|
||||
|
||||
## 相关资源
|
||||
|
||||
- 参考链接
|
||||
- 相关文档
|
||||
- 类似项目实现
|
||||
|
||||
## 优先级评估
|
||||
|
||||
| 指标 | 评估 |
|
||||
|:---|:---|
|
||||
| 影响范围 | 低 / 中 / 高 |
|
||||
| 实现难度 | 低 / 中 / 高 |
|
||||
| 紧迫程度 | 低 / 中 / 高 |
|
||||
|
||||
## 检查清单
|
||||
|
||||
- [ ] 已搜索现有 Issue,确认功能未被请求
|
||||
- [ ] 已考虑功能的向后兼容性
|
||||
- [ ] 愿意参与此功能的开发(如适用)
|
||||
89
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
89
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
75
.github/workflows/ci.yml
vendored
Normal 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
39
.github/workflows/docs.yml
vendored
Normal 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
59
.gitignore
vendored
Normal 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
184
CHANGELOG.md
Normal 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
450
CONTRIBUTING.md
Normal 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
675
LICENSE
Normal 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
56
LICENSE.CHINESE
Normal 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
496
README.md
Normal 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
462
README_GITEE.md
Normal 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
78
SECURITY.md
Normal 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
8
__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# sikuwa/__init__.py
|
||||
"""
|
||||
Sikuwa 构建工具
|
||||
一个基于 Nuitka 的 Python 项目打包工具
|
||||
"""
|
||||
|
||||
__version__ = "1.3.0"
|
||||
__author__ = "Sikuwa Team"
|
||||
24
__main__.py
Normal file
24
__main__.py
Normal 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
8
babel.cfg
Normal 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
1192
builder.py
Normal file
File diff suppressed because it is too large
Load Diff
829
cli.py
Normal file
829
cli.py
Normal 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
14
compile_translations.py
Normal 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
771
compiler.py
Normal 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
528
config.py
Normal 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
37
cpp_cache/CMakeLists.txt
Normal 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
241
cpp_cache/__init__.py
Normal 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
|
||||
})
|
||||
80
cpp_cache/pysmartcache.cpp
Normal file
80
cpp_cache/pysmartcache.cpp
Normal 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);
|
||||
}
|
||||
150
cpp_cache/pysmartcache_minimal.cpp
Normal file
150
cpp_cache/pysmartcache_minimal.cpp
Normal 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);
|
||||
}
|
||||
221
cpp_cache/pysmartcache_simple.cpp
Normal file
221
cpp_cache/pysmartcache_simple.cpp
Normal 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
30
cpp_cache/setup.py
Normal 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
393
cpp_cache/smart_cache.cpp
Normal 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
153
cpp_cache/smart_cache.h
Normal 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
|
||||
69
cpp_cache/smart_cache_minimal.cpp
Normal file
69
cpp_cache/smart_cache_minimal.cpp
Normal 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);
|
||||
}
|
||||
46
cpp_cache/smart_cache_minimal.h
Normal file
46
cpp_cache/smart_cache_minimal.h
Normal 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
|
||||
214
cpp_cache/smart_cache_simple.cpp
Normal file
214
cpp_cache/smart_cache_simple.cpp
Normal 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();
|
||||
}
|
||||
77
cpp_cache/smart_cache_simple.h
Normal file
77
cpp_cache/smart_cache_simple.h
Normal 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
77
i18n.py
Normal 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
34
i18n/__init__.py
Normal 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
|
||||
BIN
i18n/locales/en_US/LC_MESSAGES/sikuwa.mo
Normal file
BIN
i18n/locales/en_US/LC_MESSAGES/sikuwa.mo
Normal file
Binary file not shown.
210
i18n/locales/en_US/LC_MESSAGES/sikuwa.po
Normal file
210
i18n/locales/en_US/LC_MESSAGES/sikuwa.po
Normal 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
211
i18n/sikuwa.pot
Normal 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
84
incremental/__init__.py
Normal 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
396
incremental/analyzer.py
Normal 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)
|
||||
322
incremental/compiler_integration.py
Normal file
322
incremental/compiler_integration.py
Normal 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
778
incremental/core.py
Normal 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)
|
||||
45
incremental/cpp/CMakeLists.txt
Normal file
45
incremental/cpp/CMakeLists.txt
Normal 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 .)
|
||||
777
incremental/cpp/incremental_core.cpp
Normal file
777
incremental/cpp/incremental_core.cpp
Normal 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
|
||||
283
incremental/cpp/incremental_core.h
Normal file
283
incremental/cpp/incremental_core.h
Normal 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
|
||||
130
incremental/cpp/pybind_incremental.cpp
Normal file
130
incremental/cpp/pybind_incremental.cpp
Normal 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
295
incremental/demo.py
Normal 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
556
incremental/smart_cache.py
Normal 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)
|
||||
2
incremental/tests/__init__.py
Normal file
2
incremental/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# sikuwa/incremental/tests/__init__.py
|
||||
"""减量编译测试包"""
|
||||
360
incremental/tests/test_incremental.py
Normal file
360
incremental/tests/test_incremental.py
Normal 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
491
log.py
Normal 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
68
nuitka_loader.py
Normal 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
317
parser.py
Normal 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
47
sikuwa.toml
Normal 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
100
sikuwa_native_example.toml
Normal 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
|
||||
Reference in New Issue
Block a user