infinity_make 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infinity_make-1.0.0/PKG-INFO +272 -0
- infinity_make-1.0.0/README.md +264 -0
- infinity_make-1.0.0/pyproject.toml +79 -0
- infinity_make-1.0.0/setup.cfg +4 -0
- infinity_make-1.0.0/src/infinity_make/__init__.py +16 -0
- infinity_make-1.0.0/src/infinity_make/__main__.py +8 -0
- infinity_make-1.0.0/src/infinity_make/executor.py +286 -0
- infinity_make-1.0.0/src/infinity_make/main.py +151 -0
- infinity_make-1.0.0/src/infinity_make/nixenv.py +395 -0
- infinity_make-1.0.0/src/infinity_make/py.typed +0 -0
- infinity_make-1.0.0/src/infinity_make/schemas/env.inft +28 -0
- infinity_make-1.0.0/src/infinity_make/vfs/__init__.py +3 -0
- infinity_make-1.0.0/src/infinity_make/vfs/vfs.py +78 -0
- infinity_make-1.0.0/src/infinity_make.egg-info/PKG-INFO +272 -0
- infinity_make-1.0.0/src/infinity_make.egg-info/SOURCES.txt +21 -0
- infinity_make-1.0.0/src/infinity_make.egg-info/dependency_links.txt +1 -0
- infinity_make-1.0.0/src/infinity_make.egg-info/entry_points.txt +2 -0
- infinity_make-1.0.0/src/infinity_make.egg-info/requires.txt +1 -0
- infinity_make-1.0.0/src/infinity_make.egg-info/top_level.txt +1 -0
- infinity_make-1.0.0/tests/test_executor.py +400 -0
- infinity_make-1.0.0/tests/test_main.py +238 -0
- infinity_make-1.0.0/tests/test_nixenv.py +621 -0
- infinity_make-1.0.0/tests/test_vfs.py +69 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: infinity_make
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 基于 InfinityData 的自定义构建系统
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: infinity_data[tool]>=3.2.0
|
|
8
|
+
|
|
9
|
+
# infinity_make
|
|
10
|
+
|
|
11
|
+
基于 [InfinityData](https://github.com/yinbailiang/infinity_data)(`.infd` 声明式配置语言)的自定义构建系统。
|
|
12
|
+
|
|
13
|
+
`infmake.infd` 是一棵**操作树**:每个节点是一个带内嵌 Python 实现的模板实例。编译 `.infd` → 执行操作树 → 输出物化 JSON 产物,全程带子图去重、VFS 注入与持久化节点缓存。
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv run infmake -i infmake.infd --dev -o make_result.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 特性
|
|
20
|
+
|
|
21
|
+
- **操作树构建**:`.infd` 声明构建流程,节点 = 模板 + 数据字段 + 内嵌 `impl`(Python)
|
|
22
|
+
- **子图 hash 去重**:相同(模板 + 实现 + 物化输入)的子图只求值一次,结果复用
|
|
23
|
+
- **持久化节点缓存**:节点物化结果按 hash 落盘(`<workspace>/node_cache/<hash>.json`),跨运行复用
|
|
24
|
+
- **VFS 注入**:`impl` 签名声明 `vfs` 参数即注入构建工作区(临时目录 + 按 key 缓存)
|
|
25
|
+
- **Nix 环境引导**:`env.infd` → 自动 `nix develop` 重入构建(flake / impure 两种模式)
|
|
26
|
+
- **沙盒安全**:默认零信任 `deny_all()`;`--dev` 开发模式 `full_access()`(`!from` / `!file` / `!env` 全开)
|
|
27
|
+
- **可审计产物**:输出为自包含的物化 JSON 树
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
30
|
+
|
|
31
|
+
需要 Python ≥ 3.14。
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 作为项目依赖(uv 本地路径 / 已发布包)
|
|
35
|
+
uv add --editable /path/to/infinity_make
|
|
36
|
+
|
|
37
|
+
# 或直接在本仓库安装
|
|
38
|
+
uv sync
|
|
39
|
+
uv run infmake --help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
安装后提供 `infmake` 命令(`python -m infinity_make` 亦可)。
|
|
43
|
+
|
|
44
|
+
## 快速开始
|
|
45
|
+
|
|
46
|
+
一个最小示例,三件套:模板文件 → 构建配置 → 运行。
|
|
47
|
+
|
|
48
|
+
**1. 模板** `templates/hello.inft`(`.inft` = 仅模板定义的源文件):
|
|
49
|
+
|
|
50
|
+
```infd
|
|
51
|
+
~Hello {
|
|
52
|
+
name: str = "world"
|
|
53
|
+
|
|
54
|
+
_gen: dict = {
|
|
55
|
+
message: str = ""
|
|
56
|
+
}
|
|
57
|
+
_impl: str = ```python
|
|
58
|
+
def impl(name: str) -> dict[str, str]:
|
|
59
|
+
return {"message": f"hello, {name}!"}
|
|
60
|
+
```
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- `_gen`:声明 `impl` 产出的字段(元数据,不进物化记录)
|
|
65
|
+
- `_impl`:内嵌 Python 源码(三重反引号多行字符串),约定定义名为 `impl` 的函数
|
|
66
|
+
- `impl` 收到的参数是**降维后的普通 Python 值**(`str` / `int` / `list` / `dict` / `PurePosixPath`),返回的 `dict` 合并回节点记录
|
|
67
|
+
|
|
68
|
+
**2. 构建配置** `infmake.infd`:
|
|
69
|
+
|
|
70
|
+
```infd
|
|
71
|
+
!from p"templates/hello.inft" import Hello
|
|
72
|
+
|
|
73
|
+
!var Hello(name = "world") import . as hello
|
|
74
|
+
|
|
75
|
+
target = $hello
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `!from ... import`:导入模板
|
|
79
|
+
- `!var ... import . as ...`:定义变量(节点实例)
|
|
80
|
+
- `target`:顶层入口字段,即操作树根
|
|
81
|
+
|
|
82
|
+
**3. 运行**:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv run infmake -i infmake.infd --dev -o make_result.json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
产物 `make_result.json`:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"name": "world",
|
|
93
|
+
"message": "hello, world!"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> 使用了 `!from` / `!file` / `!env` 的配置必须加 `--dev`(默认沙盒为零信任 `deny_all()`,禁止导入)。
|
|
98
|
+
|
|
99
|
+
## 命令行
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
usage: infmake [-h] [-i INPUT] [-o OUTPUT] [-e ENV] [-w WORKSPACE] [--dev] [--run CMD]
|
|
103
|
+
|
|
104
|
+
options:
|
|
105
|
+
-h, --help 显示帮助
|
|
106
|
+
-i, --input INPUT 构建配置(默认 infmake.infd)
|
|
107
|
+
-o, --output OUTPUT 构建结果位置(默认 make_result.json)
|
|
108
|
+
-e, --env ENV Nix 环境声明文件(默认 <input> 同目录 env.infd)
|
|
109
|
+
-w, --workspace WORKSPACE
|
|
110
|
+
VFS 工作区目录(默认 <input> 同目录 .builder)
|
|
111
|
+
--dev 开发模式沙盒(full_access:!from / !file / !env 全开)
|
|
112
|
+
--run CMD 进入 env.infd 的 Nix 环境运行命令(不构建),如 --run "code ."
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
执行流程:加载 `env.infd`(存在则引导 Nix 环境重入)→ 编译 `infmake.infd` → 执行操作树 → 输出物化 JSON。
|
|
116
|
+
|
|
117
|
+
## Nix 环境(env.infd)
|
|
118
|
+
|
|
119
|
+
`infmake.infd` 同目录放置 `env.infd`(纯数据,经包内 `schemas/env.inft` 的 `EnvSpec` 模板严格校验)后,构建会自动 `nix develop` 重入,环境内 `!env import` 可用:
|
|
120
|
+
|
|
121
|
+
```infd
|
|
122
|
+
packages = [
|
|
123
|
+
"clang",
|
|
124
|
+
"cmake",
|
|
125
|
+
"ninja",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
env = {
|
|
129
|
+
CMAKE_PREFIX_PATH = "${cmake}",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
shell_hook = ```bash
|
|
133
|
+
export NIX_HARDENING_ENABLE="pic relro"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
flake = {
|
|
137
|
+
inputs = { nixpkgs = "github:nixos/nixpkgs/nixos-26.05" }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
clone = [
|
|
141
|
+
{
|
|
142
|
+
url = "https://github.com/foo/bar.git"
|
|
143
|
+
dest = p"deps/bar"
|
|
144
|
+
tag = "v1.2.3"
|
|
145
|
+
}
|
|
146
|
+
{
|
|
147
|
+
url = "git@github.com:foo/baz.git"
|
|
148
|
+
dest = p"src/baz"
|
|
149
|
+
commit = "3f2a1c9d8b7e"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- `packages`:Nix 包列表
|
|
155
|
+
- `env`:环境变量,`${attr}` 自动展开为 nix 字符串插值 `${pkgs.attr}`
|
|
156
|
+
- `shell_hook`:进入环境后、构建前执行的 bash(再 `exec` 构建)
|
|
157
|
+
- `flake = {...}`:显式启用 **flake 模式**(自动生成 `flake.nix` + `flake.lock`,commit 级锁定 nixpkgs);缺省 = impure `nix develop --expr`
|
|
158
|
+
- `clone`:可选;**git 项目克隆声明**,在构建/进入环境前把项目克隆到指定位置(相对 `env.infd` 所在目录)。每项必须**锁 tag 或 commit(二选一)**,克隆后校验 `HEAD == 锁定提交` 保证可复现:
|
|
159
|
+
- `url`:git 仓库地址(必填)
|
|
160
|
+
- `dest`:目标目录(`p"..."` 路径字面量)
|
|
161
|
+
- `tag`:锁定 tag
|
|
162
|
+
- `commit`:锁定 commit(非分支/标签顶端的提交也会显式拉取)
|
|
163
|
+
- 幂等:目标已存在且是 git 仓库时收敛远端并拉取、检出锁定;已存在但非 git 仓库则报错不覆盖
|
|
164
|
+
|
|
165
|
+
重入通过环境变量 `INFMAKE_NIX_ENV`(= env.infd 语法层指纹)防死循环;`env.infd` 内容变化才触发重建。
|
|
166
|
+
|
|
167
|
+
`--run` 可只进入该环境运行命令而不构建:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
uv run infmake --run "code ." # 在 Nix 环境里打开 VS Code
|
|
171
|
+
uv run infmake --run "bash gen_pkgconfig.sh"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 进阶用法
|
|
175
|
+
|
|
176
|
+
### 模板与实现外置
|
|
177
|
+
|
|
178
|
+
实现代码可外置到 `.py` 文件,用 `!file ... as raw` 导入为字符串(模板内再引用):
|
|
179
|
+
|
|
180
|
+
```infd
|
|
181
|
+
# core.inft
|
|
182
|
+
!file p"impl/compile.py" as raw import . as CompileImpl
|
|
183
|
+
|
|
184
|
+
~Compile {
|
|
185
|
+
compiler: Compiler
|
|
186
|
+
source: File
|
|
187
|
+
flags: <list, each(str)> = []
|
|
188
|
+
_gen: dict = {
|
|
189
|
+
object: str = "",
|
|
190
|
+
compile_command: object = {},
|
|
191
|
+
}
|
|
192
|
+
_impl: str = $CompileImpl
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
# impl/compile.py
|
|
198
|
+
def impl(compiler: dict, source: dict, flags: list[str], vfs) -> dict:
|
|
199
|
+
...
|
|
200
|
+
return {"object": obj, "compile_command": cmd}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 内容快照节点(~File)
|
|
204
|
+
|
|
205
|
+
`path` 类型的字段(`p"src"`)由 `impl` 收到 `PurePosixPath`;若要**文件内容**参与节点 hash(内容变化 → 下游缓存自动失效),用 `~File` 节点对文件/目录算内容指纹,下游 `$file` 引用它即可(指纹自动传播进下游节点 key):
|
|
206
|
+
|
|
207
|
+
```infd
|
|
208
|
+
~File {
|
|
209
|
+
path: path
|
|
210
|
+
ignore: <list, each(str)> = []
|
|
211
|
+
mtime: bool = false # mtime 快照加速
|
|
212
|
+
_cache: bool = false # 内容检测节点:每次执行重读
|
|
213
|
+
_gen: dict = {
|
|
214
|
+
path: str = "",
|
|
215
|
+
hash: str = "",
|
|
216
|
+
entries: int = 0,
|
|
217
|
+
exists: bool = false,
|
|
218
|
+
}
|
|
219
|
+
_impl: str = ```python
|
|
220
|
+
def impl(path: str, ignore: list[str], mtime: bool, vfs) -> dict:
|
|
221
|
+
... # 递归内容指纹(sha256)
|
|
222
|
+
```
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### VFS 注入
|
|
227
|
+
|
|
228
|
+
`impl` 签名含 `vfs` 参数即注入节点作用域代理(缓存自动以当前节点 hash 为 key):
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
def impl(tag, vfs):
|
|
232
|
+
tmp = vfs.tmp("stage") # 一次性临时目录(每次调用新建)
|
|
233
|
+
cache = vfs.cache("repo") # → cache/<节点hash>/repo(跨运行复用)
|
|
234
|
+
shared = vfs.global_.cache("pcm") # → cache/pcm(全局共享)
|
|
235
|
+
return {"tmp_dir": str(tmp), "cache_dir": str(cache)}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 节点缓存
|
|
239
|
+
|
|
240
|
+
- 默认开启:节点物化结果经 `dump_to_json` / `load_from_json` 无损往返落盘 `<workspace>/node_cache/<hash>.json`(`Decimal` / `path` / `noexist` 以自描述标记编码)
|
|
241
|
+
- 模板声明 `_cache: bool = false` 关闭(如 `~File` 内容检测)
|
|
242
|
+
|
|
243
|
+
## 项目结构
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
src/infinity_make/
|
|
247
|
+
__init__.py 公共 API(Executor / BuildError / main)
|
|
248
|
+
__main__.py python -m infinity_make 入口
|
|
249
|
+
main.py CLI 薄壳:argparse → 编译 → 执行 → 输出 JSON
|
|
250
|
+
executor.py 操作树执行器(子图 hash 去重 + VFS 注入 + 节点缓存)
|
|
251
|
+
nixenv.py Nix 环境引导(env.infd → nix develop 重入 / --run)
|
|
252
|
+
vfs/ 构建工作区 VFS(tmp + cache + node_cache)
|
|
253
|
+
schemas/env.inft env.infd 的 EnvSpec schema(包内分发)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
工作区(默认 `<input> 同目录 .builder/`):
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
.builder/
|
|
260
|
+
tmp/ 一次性临时目录(每次构建从干净区开始,缓存保留)
|
|
261
|
+
cache/ VFS 缓存(<key>/<name>,跨运行复用)
|
|
262
|
+
node_cache/ 节点物化缓存(<hash>.json)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## 开发
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
uv sync --group dev # ruff / pyright / pre-commit / pytest
|
|
269
|
+
uv run ruff check .
|
|
270
|
+
uv run pyright
|
|
271
|
+
uv run pytest
|
|
272
|
+
```
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# infinity_make
|
|
2
|
+
|
|
3
|
+
基于 [InfinityData](https://github.com/yinbailiang/infinity_data)(`.infd` 声明式配置语言)的自定义构建系统。
|
|
4
|
+
|
|
5
|
+
`infmake.infd` 是一棵**操作树**:每个节点是一个带内嵌 Python 实现的模板实例。编译 `.infd` → 执行操作树 → 输出物化 JSON 产物,全程带子图去重、VFS 注入与持久化节点缓存。
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv run infmake -i infmake.infd --dev -o make_result.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 特性
|
|
12
|
+
|
|
13
|
+
- **操作树构建**:`.infd` 声明构建流程,节点 = 模板 + 数据字段 + 内嵌 `impl`(Python)
|
|
14
|
+
- **子图 hash 去重**:相同(模板 + 实现 + 物化输入)的子图只求值一次,结果复用
|
|
15
|
+
- **持久化节点缓存**:节点物化结果按 hash 落盘(`<workspace>/node_cache/<hash>.json`),跨运行复用
|
|
16
|
+
- **VFS 注入**:`impl` 签名声明 `vfs` 参数即注入构建工作区(临时目录 + 按 key 缓存)
|
|
17
|
+
- **Nix 环境引导**:`env.infd` → 自动 `nix develop` 重入构建(flake / impure 两种模式)
|
|
18
|
+
- **沙盒安全**:默认零信任 `deny_all()`;`--dev` 开发模式 `full_access()`(`!from` / `!file` / `!env` 全开)
|
|
19
|
+
- **可审计产物**:输出为自包含的物化 JSON 树
|
|
20
|
+
|
|
21
|
+
## 安装
|
|
22
|
+
|
|
23
|
+
需要 Python ≥ 3.14。
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 作为项目依赖(uv 本地路径 / 已发布包)
|
|
27
|
+
uv add --editable /path/to/infinity_make
|
|
28
|
+
|
|
29
|
+
# 或直接在本仓库安装
|
|
30
|
+
uv sync
|
|
31
|
+
uv run infmake --help
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
安装后提供 `infmake` 命令(`python -m infinity_make` 亦可)。
|
|
35
|
+
|
|
36
|
+
## 快速开始
|
|
37
|
+
|
|
38
|
+
一个最小示例,三件套:模板文件 → 构建配置 → 运行。
|
|
39
|
+
|
|
40
|
+
**1. 模板** `templates/hello.inft`(`.inft` = 仅模板定义的源文件):
|
|
41
|
+
|
|
42
|
+
```infd
|
|
43
|
+
~Hello {
|
|
44
|
+
name: str = "world"
|
|
45
|
+
|
|
46
|
+
_gen: dict = {
|
|
47
|
+
message: str = ""
|
|
48
|
+
}
|
|
49
|
+
_impl: str = ```python
|
|
50
|
+
def impl(name: str) -> dict[str, str]:
|
|
51
|
+
return {"message": f"hello, {name}!"}
|
|
52
|
+
```
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `_gen`:声明 `impl` 产出的字段(元数据,不进物化记录)
|
|
57
|
+
- `_impl`:内嵌 Python 源码(三重反引号多行字符串),约定定义名为 `impl` 的函数
|
|
58
|
+
- `impl` 收到的参数是**降维后的普通 Python 值**(`str` / `int` / `list` / `dict` / `PurePosixPath`),返回的 `dict` 合并回节点记录
|
|
59
|
+
|
|
60
|
+
**2. 构建配置** `infmake.infd`:
|
|
61
|
+
|
|
62
|
+
```infd
|
|
63
|
+
!from p"templates/hello.inft" import Hello
|
|
64
|
+
|
|
65
|
+
!var Hello(name = "world") import . as hello
|
|
66
|
+
|
|
67
|
+
target = $hello
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- `!from ... import`:导入模板
|
|
71
|
+
- `!var ... import . as ...`:定义变量(节点实例)
|
|
72
|
+
- `target`:顶层入口字段,即操作树根
|
|
73
|
+
|
|
74
|
+
**3. 运行**:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv run infmake -i infmake.infd --dev -o make_result.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
产物 `make_result.json`:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"name": "world",
|
|
85
|
+
"message": "hello, world!"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> 使用了 `!from` / `!file` / `!env` 的配置必须加 `--dev`(默认沙盒为零信任 `deny_all()`,禁止导入)。
|
|
90
|
+
|
|
91
|
+
## 命令行
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
usage: infmake [-h] [-i INPUT] [-o OUTPUT] [-e ENV] [-w WORKSPACE] [--dev] [--run CMD]
|
|
95
|
+
|
|
96
|
+
options:
|
|
97
|
+
-h, --help 显示帮助
|
|
98
|
+
-i, --input INPUT 构建配置(默认 infmake.infd)
|
|
99
|
+
-o, --output OUTPUT 构建结果位置(默认 make_result.json)
|
|
100
|
+
-e, --env ENV Nix 环境声明文件(默认 <input> 同目录 env.infd)
|
|
101
|
+
-w, --workspace WORKSPACE
|
|
102
|
+
VFS 工作区目录(默认 <input> 同目录 .builder)
|
|
103
|
+
--dev 开发模式沙盒(full_access:!from / !file / !env 全开)
|
|
104
|
+
--run CMD 进入 env.infd 的 Nix 环境运行命令(不构建),如 --run "code ."
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
执行流程:加载 `env.infd`(存在则引导 Nix 环境重入)→ 编译 `infmake.infd` → 执行操作树 → 输出物化 JSON。
|
|
108
|
+
|
|
109
|
+
## Nix 环境(env.infd)
|
|
110
|
+
|
|
111
|
+
`infmake.infd` 同目录放置 `env.infd`(纯数据,经包内 `schemas/env.inft` 的 `EnvSpec` 模板严格校验)后,构建会自动 `nix develop` 重入,环境内 `!env import` 可用:
|
|
112
|
+
|
|
113
|
+
```infd
|
|
114
|
+
packages = [
|
|
115
|
+
"clang",
|
|
116
|
+
"cmake",
|
|
117
|
+
"ninja",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
env = {
|
|
121
|
+
CMAKE_PREFIX_PATH = "${cmake}",
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
shell_hook = ```bash
|
|
125
|
+
export NIX_HARDENING_ENABLE="pic relro"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
flake = {
|
|
129
|
+
inputs = { nixpkgs = "github:nixos/nixpkgs/nixos-26.05" }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
clone = [
|
|
133
|
+
{
|
|
134
|
+
url = "https://github.com/foo/bar.git"
|
|
135
|
+
dest = p"deps/bar"
|
|
136
|
+
tag = "v1.2.3"
|
|
137
|
+
}
|
|
138
|
+
{
|
|
139
|
+
url = "git@github.com:foo/baz.git"
|
|
140
|
+
dest = p"src/baz"
|
|
141
|
+
commit = "3f2a1c9d8b7e"
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- `packages`:Nix 包列表
|
|
147
|
+
- `env`:环境变量,`${attr}` 自动展开为 nix 字符串插值 `${pkgs.attr}`
|
|
148
|
+
- `shell_hook`:进入环境后、构建前执行的 bash(再 `exec` 构建)
|
|
149
|
+
- `flake = {...}`:显式启用 **flake 模式**(自动生成 `flake.nix` + `flake.lock`,commit 级锁定 nixpkgs);缺省 = impure `nix develop --expr`
|
|
150
|
+
- `clone`:可选;**git 项目克隆声明**,在构建/进入环境前把项目克隆到指定位置(相对 `env.infd` 所在目录)。每项必须**锁 tag 或 commit(二选一)**,克隆后校验 `HEAD == 锁定提交` 保证可复现:
|
|
151
|
+
- `url`:git 仓库地址(必填)
|
|
152
|
+
- `dest`:目标目录(`p"..."` 路径字面量)
|
|
153
|
+
- `tag`:锁定 tag
|
|
154
|
+
- `commit`:锁定 commit(非分支/标签顶端的提交也会显式拉取)
|
|
155
|
+
- 幂等:目标已存在且是 git 仓库时收敛远端并拉取、检出锁定;已存在但非 git 仓库则报错不覆盖
|
|
156
|
+
|
|
157
|
+
重入通过环境变量 `INFMAKE_NIX_ENV`(= env.infd 语法层指纹)防死循环;`env.infd` 内容变化才触发重建。
|
|
158
|
+
|
|
159
|
+
`--run` 可只进入该环境运行命令而不构建:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
uv run infmake --run "code ." # 在 Nix 环境里打开 VS Code
|
|
163
|
+
uv run infmake --run "bash gen_pkgconfig.sh"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 进阶用法
|
|
167
|
+
|
|
168
|
+
### 模板与实现外置
|
|
169
|
+
|
|
170
|
+
实现代码可外置到 `.py` 文件,用 `!file ... as raw` 导入为字符串(模板内再引用):
|
|
171
|
+
|
|
172
|
+
```infd
|
|
173
|
+
# core.inft
|
|
174
|
+
!file p"impl/compile.py" as raw import . as CompileImpl
|
|
175
|
+
|
|
176
|
+
~Compile {
|
|
177
|
+
compiler: Compiler
|
|
178
|
+
source: File
|
|
179
|
+
flags: <list, each(str)> = []
|
|
180
|
+
_gen: dict = {
|
|
181
|
+
object: str = "",
|
|
182
|
+
compile_command: object = {},
|
|
183
|
+
}
|
|
184
|
+
_impl: str = $CompileImpl
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# impl/compile.py
|
|
190
|
+
def impl(compiler: dict, source: dict, flags: list[str], vfs) -> dict:
|
|
191
|
+
...
|
|
192
|
+
return {"object": obj, "compile_command": cmd}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 内容快照节点(~File)
|
|
196
|
+
|
|
197
|
+
`path` 类型的字段(`p"src"`)由 `impl` 收到 `PurePosixPath`;若要**文件内容**参与节点 hash(内容变化 → 下游缓存自动失效),用 `~File` 节点对文件/目录算内容指纹,下游 `$file` 引用它即可(指纹自动传播进下游节点 key):
|
|
198
|
+
|
|
199
|
+
```infd
|
|
200
|
+
~File {
|
|
201
|
+
path: path
|
|
202
|
+
ignore: <list, each(str)> = []
|
|
203
|
+
mtime: bool = false # mtime 快照加速
|
|
204
|
+
_cache: bool = false # 内容检测节点:每次执行重读
|
|
205
|
+
_gen: dict = {
|
|
206
|
+
path: str = "",
|
|
207
|
+
hash: str = "",
|
|
208
|
+
entries: int = 0,
|
|
209
|
+
exists: bool = false,
|
|
210
|
+
}
|
|
211
|
+
_impl: str = ```python
|
|
212
|
+
def impl(path: str, ignore: list[str], mtime: bool, vfs) -> dict:
|
|
213
|
+
... # 递归内容指纹(sha256)
|
|
214
|
+
```
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### VFS 注入
|
|
219
|
+
|
|
220
|
+
`impl` 签名含 `vfs` 参数即注入节点作用域代理(缓存自动以当前节点 hash 为 key):
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
def impl(tag, vfs):
|
|
224
|
+
tmp = vfs.tmp("stage") # 一次性临时目录(每次调用新建)
|
|
225
|
+
cache = vfs.cache("repo") # → cache/<节点hash>/repo(跨运行复用)
|
|
226
|
+
shared = vfs.global_.cache("pcm") # → cache/pcm(全局共享)
|
|
227
|
+
return {"tmp_dir": str(tmp), "cache_dir": str(cache)}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 节点缓存
|
|
231
|
+
|
|
232
|
+
- 默认开启:节点物化结果经 `dump_to_json` / `load_from_json` 无损往返落盘 `<workspace>/node_cache/<hash>.json`(`Decimal` / `path` / `noexist` 以自描述标记编码)
|
|
233
|
+
- 模板声明 `_cache: bool = false` 关闭(如 `~File` 内容检测)
|
|
234
|
+
|
|
235
|
+
## 项目结构
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
src/infinity_make/
|
|
239
|
+
__init__.py 公共 API(Executor / BuildError / main)
|
|
240
|
+
__main__.py python -m infinity_make 入口
|
|
241
|
+
main.py CLI 薄壳:argparse → 编译 → 执行 → 输出 JSON
|
|
242
|
+
executor.py 操作树执行器(子图 hash 去重 + VFS 注入 + 节点缓存)
|
|
243
|
+
nixenv.py Nix 环境引导(env.infd → nix develop 重入 / --run)
|
|
244
|
+
vfs/ 构建工作区 VFS(tmp + cache + node_cache)
|
|
245
|
+
schemas/env.inft env.infd 的 EnvSpec schema(包内分发)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
工作区(默认 `<input> 同目录 .builder/`):
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
.builder/
|
|
252
|
+
tmp/ 一次性临时目录(每次构建从干净区开始,缓存保留)
|
|
253
|
+
cache/ VFS 缓存(<key>/<name>,跨运行复用)
|
|
254
|
+
node_cache/ 节点物化缓存(<hash>.json)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## 开发
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
uv sync --group dev # ruff / pyright / pre-commit / pytest
|
|
261
|
+
uv run ruff check .
|
|
262
|
+
uv run pyright
|
|
263
|
+
uv run pytest
|
|
264
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "infinity_make"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "基于 InfinityData 的自定义构建系统"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"infinity_data[tool]>=3.2.0"
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[project.scripts]
|
|
12
|
+
infmake = "infinity_make.main:main"
|
|
13
|
+
|
|
14
|
+
[dependency-groups]
|
|
15
|
+
# 测试组:仅运行测试所需(CI test job 只装这组,更快)
|
|
16
|
+
test = [
|
|
17
|
+
"pytest>=8.0",
|
|
18
|
+
"pytest-cov>=5.0",
|
|
19
|
+
]
|
|
20
|
+
# 开发组:lint/类型/钩子 + 测试组(本地开发与 CI pre-commit job 装这组)
|
|
21
|
+
dev = [
|
|
22
|
+
"ruff>=0.15.21",
|
|
23
|
+
"pyright",
|
|
24
|
+
"pre-commit>=4.0",
|
|
25
|
+
"interrogate>=1.7.0",
|
|
26
|
+
{ include-group = "test" },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["setuptools>=61.0"]
|
|
31
|
+
build-backend = "setuptools.build_meta"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools]
|
|
34
|
+
package-dir = {"" = "src"}
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.package-data]
|
|
40
|
+
infinity_make = ["schemas/*.inft", "py.typed"]
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 120
|
|
44
|
+
target-version = "py312"
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = [
|
|
48
|
+
"E", # pycodestyle errors
|
|
49
|
+
"F", # pyflakes
|
|
50
|
+
"I", # isort
|
|
51
|
+
"ASYNC", # async/await best practices
|
|
52
|
+
"PLE", # pylint errors
|
|
53
|
+
]
|
|
54
|
+
ignore = [
|
|
55
|
+
"E501", # line too long (handled by formatter)
|
|
56
|
+
"ASYNC109", # async function with timeout parameter (intentional API design)
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint.isort]
|
|
60
|
+
known-first-party = ["infinity_make"]
|
|
61
|
+
# 带 as 别名的导入合并到同一 from 语句(如 simple_lang.common 的助手别名)
|
|
62
|
+
combine-as-imports = true
|
|
63
|
+
|
|
64
|
+
[tool.ruff.format]
|
|
65
|
+
indent-style = "space"
|
|
66
|
+
quote-style = "single"
|
|
67
|
+
line-ending = "lf"
|
|
68
|
+
|
|
69
|
+
[tool.pyright]
|
|
70
|
+
typeCheckingMode = "strict"
|
|
71
|
+
reportMissingTypeStubs = false
|
|
72
|
+
reportMissingImports = "error"
|
|
73
|
+
exclude = ["**/node_modules", "**/__pycache__", "**/.*", ".venv"]
|
|
74
|
+
venvPath = "."
|
|
75
|
+
venv = ".venv"
|
|
76
|
+
|
|
77
|
+
[tool.interrogate]
|
|
78
|
+
fail-under = 70
|
|
79
|
+
ignore-regex = ["^test_"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""infinity_make —— expr 模式构建系统:infmake.infd 是一棵操作树。
|
|
2
|
+
|
|
3
|
+
分层结构:
|
|
4
|
+
|
|
5
|
+
- :mod:`infinity_make.executor`:操作树执行器(子图去重 + VFS 注入 + 节点缓存)
|
|
6
|
+
- :mod:`infinity_make.nixenv`:Nix 环境引导(env.infd → ``nix develop`` 重入 / ``--run``)
|
|
7
|
+
- :mod:`infinity_make.main`:CLI 薄壳(``infmake`` 入口)
|
|
8
|
+
- :mod:`infinity_make.vfs`:构建工作区 VFS(临时目录 + 按 key 持久缓存)
|
|
9
|
+
|
|
10
|
+
公共 API::class:`Executor`、:class:`BuildError`、:func:`main`。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from infinity_make.executor import BuildError, Executor
|
|
14
|
+
from infinity_make.main import main
|
|
15
|
+
|
|
16
|
+
__all__ = ['BuildError', 'Executor', 'main']
|