Metadata-Version: 2.4
Name: infinity_make
Version: 1.1.0
Summary: 基于 InfinityData 的自定义构建系统
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: infinity_data[tool]>=3.2.0

# infinity_make

[![PyPI - Version](https://img.shields.io/pypi/v/infinity_make)](https://pypi.org/project/infinity_make/)
[![PyPI - Python](https://img.shields.io/pypi/pyversions/infinity_make)](https://pypi.org/project/infinity_make/)

基于 [InfinityData](https://github.com/yinbailiang/infinity_data)（`.infd` 声明式配置语言）的自定义构建系统。

`infmake.infd` 是一棵**操作树**：每个节点是一个带内嵌 Python 实现的模板实例。编译 `.infd` → 执行操作树 → 输出物化 JSON 产物，全程带子图去重、VFS 注入与持久化节点缓存。

```bash
uv run infmake -i infmake.infd --dev -o make_result.json
```

## 特性

- **操作树构建**：`.infd` 声明构建流程，节点 = 模板 + 数据字段 + 内嵌 `impl`（Python）
- **子图 hash 去重**：相同（模板 + 实现 + 物化输入）的子图只求值一次，结果复用
- **持久化节点缓存**：节点物化结果按 hash 落盘（`<workspace>/node_cache/<hash>.json`），跨运行复用
- **VFS / id 注入**：`impl` 签名声明 `vfs` / `id` 参数即注入构建工作区与节点标识（临时目录 + 按 key 缓存 / NodeId 指纹）
- **Nix 环境引导**：`env.infd` → 自动 `nix develop` 重入构建（flake / impure 两种模式）
- **沙盒安全**：默认零信任 `deny_all()`；`--dev` 开发模式 `full_access()`（`!from` / `!file` / `!env` 全开）
- **可审计产物**：输出为自包含的物化 JSON 树

## 安装

需要 Python ≥ 3.12。包已发布到 PyPI（包名 `infinity_make`）。

```bash
# 作为项目依赖（从 PyPI 安装）
uv add infinity_make

# 或用 pip
pip install infinity_make

# 本地开发（本仓库内）
uv sync
uv run infmake --help
```

安装后提供 `infmake` 命令（`python -m infinity_make` 亦可）。

## 快速开始

一个最小示例，三件套：模板文件 → 构建配置 → 运行。

**1. 模板** `templates/hello.inft`（`.inft` = 仅模板定义的源文件）：

```infd
~Hello {
    name: str = "world"

    _gen: dict = {
        message: str = ""
    }
    _impl: str = ```python
def impl(name: str) -> dict[str, str]:
    return {"message": f"hello, {name}!"}
    ```
}
```

- `_gen`：声明 `impl` 产出的字段（元数据，不进物化记录）
- `_impl`：内嵌 Python 源码（三重反引号多行字符串），约定定义名为 `impl` 的函数
- `impl` 收到的参数是**降维后的普通 Python 值**（`str` / `int` / `list` / `dict` / `PurePosixPath`），返回的 `dict` 合并回节点记录

**2. 构建配置** `infmake.infd`：

```infd
!from p"templates/hello.inft" import Hello

!var Hello(name = "world") import . as hello

target = $hello
```

- `!from ... import`：导入模板
- `!var ... import . as ...`：定义变量（节点实例）
- `target`：顶层入口字段，即操作树根

**3. 运行**：

```bash
uv run infmake -i infmake.infd --dev -o make_result.json
```

产物 `make_result.json`：

```json
{
  "name": "world",
  "message": "hello, world!"
}
```

> 使用了 `!from` / `!file` / `!env` 的配置必须加 `--dev`（默认沙盒为零信任 `deny_all()`，禁止导入）。

## 命令行

```
usage: infmake [-h] [-i INPUT] [-o OUTPUT] [-e ENV] [-w WORKSPACE] [--dev] [--run CMD]

options:
  -h, --help            显示帮助
  -i, --input INPUT     构建配置（默认 infmake.infd）
  -o, --output OUTPUT   构建结果位置（默认 make_result.json）
  -e, --env ENV         Nix 环境声明文件（默认 <input> 同目录 env.infd）
  -w, --workspace WORKSPACE
                        VFS 工作区目录（默认 <input> 同目录 .builder）
  --dev                 开发模式沙盒（full_access：!from / !file / !env 全开）
  --run CMD             进入 env.infd 的 Nix 环境运行命令（不构建），如 --run "code ."
```

执行流程：加载 `env.infd`（存在则引导 Nix 环境重入）→ 编译 `infmake.infd` → 执行操作树 → 输出物化 JSON。

## Nix 环境（env.infd）

`infmake.infd` 同目录放置 `env.infd`（纯数据，经包内 `schemas/env.inft` 的 `EnvSpec` 模板严格校验）后，构建会自动 `nix develop` 重入，环境内 `!env import` 可用：

```infd
packages = [
    "clang",
    "cmake",
    "ninja",
]

env = {
    CMAKE_PREFIX_PATH = "${cmake}",
}

shell_hook = ```bash
export NIX_HARDENING_ENABLE="pic relro"
```

flake = {
    inputs = { nixpkgs = "github:nixos/nixpkgs/nixos-26.05" }
}

clone = [
    {
        url = "https://github.com/foo/bar.git"
        dest = p"deps/bar"
        tag = "v1.2.3"
    }
    {
        url = "git@github.com:foo/baz.git"
        dest = p"src/baz"
        commit = "3f2a1c9d8b7e"
    }
]
```

- `packages`：Nix 包列表
- `env`：环境变量，`${attr}` 自动展开为 nix 字符串插值 `${pkgs.attr}`
- `shell_hook`：进入环境后、构建前执行的 bash（再 `exec` 构建）
- `flake = {...}`：显式启用 **flake 模式**（自动生成 `flake.nix` + `flake.lock`，commit 级锁定 nixpkgs）；缺省 = impure `nix develop --expr`
- `clone`：可选；**git 项目克隆声明**，在构建/进入环境前把项目克隆到指定位置（相对 `env.infd` 所在目录）。每项必须**锁 tag 或 commit（二选一）**，克隆后校验 `HEAD == 锁定提交` 保证可复现：
  - `url`：git 仓库地址（必填）
  - `dest`：目标目录（`p"..."` 路径字面量）
  - `tag`：锁定 tag
  - `commit`：锁定 commit（非分支/标签顶端的提交也会显式拉取）
  - 幂等：目标已存在且是 git 仓库时收敛远端并拉取、检出锁定；已存在但非 git 仓库则报错不覆盖

重入通过环境变量 `INFMAKE_NIX_ENV`（= env.infd 语法层指纹）防死循环；`env.infd` 内容变化才触发重建。

`--run` 可只进入该环境运行命令而不构建：

```bash
uv run infmake --run "code ."        # 在 Nix 环境里打开 VS Code
uv run infmake --run "bash gen_pkgconfig.sh"
```

## 进阶用法

### 模板与实现外置

实现代码可外置到 `.py` 文件，用 `!file ... as raw` 导入为字符串（模板内再引用）：

```infd
# core.inft
!file p"impl/compile.py" as raw import . as CompileImpl

~Compile {
    compiler: Compiler
    source: File
    flags: <list, each(str)> = []
    _gen: dict = {
        object: str = "",
        compile_command: object = {},
    }
    _impl: str = $CompileImpl
}
```

```python
# impl/compile.py
def impl(compiler: dict, source: dict, flags: list[str], vfs) -> dict:
    ...
    return {"object": obj, "compile_command": cmd}
```

### 内容快照节点（~File）

`path` 类型的字段（`p"src"`）由 `impl` 收到 `PurePosixPath`；若要**文件内容**参与节点 hash（内容变化 → 下游缓存自动失效），用 `~File` 节点对文件/目录算内容指纹，下游 `$file` 引用它即可（指纹自动传播进下游节点 key）：

```infd
~File {
    path: path
    ignore: <list, each(str)> = []
    mtime: bool = false        # mtime 快照加速
    _cache: bool = false       # 内容检测节点：每次执行重读
    _gen: dict = {
        path: str = "",
        hash: str = "",
        entries: int = 0,
        exists: bool = false,
    }
    _impl: str = ```python
def impl(path: str, ignore: list[str], mtime: bool, vfs) -> dict:
    ...   # 递归内容指纹（sha256）
    ```
}
```

### VFS 注入

`impl` 签名含 `vfs` 参数即注入节点作用域代理（缓存自动以当前节点 hash 为 key）：

```python
def impl(tag, vfs):
    tmp = vfs.tmp("stage")            # 一次性临时目录（每次调用新建）
    cache = vfs.cache()               # → cache/<节点hash>（跨运行复用）
    shared = vfs.data("pcm")          # → data/pcm（全局持久目录，跨节点共享）
    return {"tmp_dir": str(tmp), "cache_dir": str(cache)}
```

### 节点标识（id 注入）

`impl` 签名含 `id` 参数即注入当前节点的 :class:`NodeId`（`node_hash` / `template_hash`）：

```python
def impl(tag, id):
    # id.node_hash       → 完整节点指纹（模板名 + 实现源码 + 物化输入）
    # id.template_hash   → 模板 + 实现指纹（不含输入）
    ...
```

- `node_hash`：完整节点指纹，也是持久化缓存 `<node_hash>.json` 与 `vfs.cache()` 目录的 key
- `template_hash`：不含输入的「模板 + 实现」指纹——同一模板实现、不同输入时 hash 相同

### 节点缓存

- 默认开启：节点物化结果经 `dump_to_json` / `load_from_json` 无损往返落盘 `<workspace>/node_cache/<hash>.json`（`Decimal` / `path` / `noexist` 以自描述标记编码）
- 模板声明 `_cache: bool = false` 关闭（如 `~File` 内容检测）

## 项目结构

```
src/infinity_make/
  __init__.py      公共 API（Executor / BuildError / main）
  __main__.py      python -m infinity_make 入口
  main.py          CLI 薄壳：argparse → 编译 → 执行 → 输出 JSON
  executor.py      操作树执行器（子图 hash 去重 + VFS 注入 + 节点缓存）
  nixenv.py        Nix 环境引导（env.infd → nix develop 重入 / --run）
  vfs/             构建工作区 VFS（tmp + cache + node_cache）
  schemas/env.inft env.infd 的 EnvSpec schema（包内分发）
```

工作区（默认 `<input> 同目录 .builder/`）：

```
.builder/
  tmp/         一次性临时目录（每次构建从干净区开始，缓存保留）
  data/        全局持久目录（vfs.data(name)）
  cache/       VFS 缓存（<key>，跨运行复用）
  node_cache/  节点物化缓存（<hash>.json）
```

## 开发

```bash
uv sync --group dev        # ruff / pyright / pre-commit / pytest
uv run ruff check .
uv run pyright
uv run pytest
```
