公告

FujoOS公布日延期,以仓库发布为准

首页 Loment · Potato Lompi

第一个库管理工具,用 Loment 自己写的。

Lompi 管的是 Loment 库——不是可执行文件,也不是二进制分发包。它是库系统的第一个独立实现:入口加七个模块,编成一个可执行文件,跑起来不需要 Python。它自己读源码里的 use 找出依赖,自己算身份,自己发锁。

从 0.1.4 Alpha2.3 起,它随 Loment 一起安装——装好 Loment,lompi 就在 PATH 上,不用单独装。同一种做法也用在那份 agent 指南上:装完就能写 Loment,不必另外去拿。

它和别的包管理器差在哪

一句话:pip 里最难的是「挑版本」,Lompi 里最难的是「把实例身份算准」。前者是一整个回溯求解器的工程量;后者是一条公式,但那条公式必须对得起一件反直觉的事。

依赖从源码来

依赖不用另行声明——它就是源码里的 use。于是「清单说依赖 A、代码其实不用 A」这类谎言连存在的地方都没有

身份是内容哈希

锁文件里钉的是哈希,不是版本号。所以「可复现」是算出来的,不是靠版本区间推出来的。

多版本共存

同一个库的两个版本可以同时存在于一次构建里。选版只剩一条规则——版本最大;要钉死就写 名字@版本 或用锁。没有区间,没有冲突消解策略这一整类复杂度。

那条反直觉的事

Lompi 算的不是「一个包的哈希」,是「一个实例的哈希」:

text 实例身份
id(P) = sha256( 自身源码 ⊕ 每条 name 边: 边名 0x00 子实例身份 0x00 )

边也要进哈希。因为只看自身源码是错的——同一份源码在两个构建里绑到不同的依赖上,语义不同,却会被算出同一个身份、被错误地合并成一个实例。这不是理论担忧,它长这样:

console 同一份源码,两个身份
geom 的源码逐字节相同, 但落在两个仓库里
  仓库 A 有 mathutil 0.1.0 / 0.2.0  →  geom id = 06b4cc0d3b1f…  (绑 0.2.0)
  仓库 B 只有 mathutil 0.1.0        →  geom id = 0f4bb3784d09…  (绑 0.1.0)

两个 id 不同是对的。把它们合并才是有害的——那会让「geom 用的是哪个 mathutil」这件事在构建里消失。

它跑起来是什么样

下面是真实输出,不是示意。

console 仓库里有什么
$ lompi index fixture/store
5 package(s) in store
geom 1.0.0 06b4cc0d3b1f136f
mathutil 0.1.0 53ce217a387ed58a
mathutil 0.2.0 c5f88a0519c41888
multi 2.0.0 8326d0125e3b111d
util 0.3.0 e0e90317698ff804

mathutil 出现两次不是重复——那是两个实例。第一行那个 5 package(s) 数的是实例数,不是名字数。

console 解析 / 发锁 / 验锁
$ lompi tree fixture/store multi
multi@2.0.0 8326d0125e3b
  util@0.3.0 e0e90317698f

$ lompi resolve fixture/store multi
# lompi lock v1
multi 2.0.0 8326d0125e3b111d51d050e49395400524a18ddc31eb730f7afbc9cb6a4955fb
util 0.3.0 e0e90317698ff804db92a01bce6b7a965e9e679eb6bb80b25f02c2d05207b041

$ lompi verify fixture/store multi lompi.lock
[OK] lompi: 锁与仓库一致 (169 bytes)

验锁是把整棵树重算一遍再逐字节比对。拿错一份锁,得到的是 [DIFF] 锁与仓库不一致 并以 1 退出——不是「差不多能用」。

取件那一半

前面那些命令只读。这一半会动你的磁盘,而且它有一条不太一样的边界——先说这条。

  • 网址库是一个 git 仓库 —— 布局和 store 一模一样(<名字>/<版本>/*.lomt),没有服务端要运维。地址写在执行文件旁边lompi.conf 里,而那份配置本身就是 Loment 源码
  • fetch 不联网,它打印计划 —— Lompi 自己做不到联网:PE 上那 8 个 syscall 里没有 socket,垫片也没有 ws2_32。所以它把 git clone 打出来交给 shell 或 CI——和「plan 不建目录」是同一条边界。
  • install 才动盘,而且要明说 —— 默认装进全局 store--into DIR 装进项目;不带 --apply 只出计划--from-lock F 只装 F 钉死的那些实例,对不上就拒。
  • 装之前先校验 —— lompi check 逐条报:有没有 *.lomt、包名与目录名对不对、每个模块名与文件名对不对、pkg.lomp 的两个标签对不对。过不了的写 [BAD],而且说清是哪一条
console fetch 打出什么
$ lompi fetch --registry https://example.invalid/loment-registry.git
# lompi fetch plan (lompi cannot do network on PE -- no socket in the 8 syscalls)
# registry: https://example.invalid/loment-registry.git  (from --registry)
mkdir -p <global>\cache
git clone --depth 1 https://example.invalid/loment-registry.git <global>\cache\loment-registry
# to update later:  git -C <global>\cache\loment-registry pull

它现在到哪一步

  • 覆盖解析与身份,不做物化 —— 读依赖、算身份、发锁、验锁、给落盘计划。编译器那一侧的物化改名是库系统里 materialize 的活,不是它的。
  • plan 不建目录 —— 工具链的 Windows PE 目标没有 mkdir,所以它只打计划,deps/ 下的目录要自己先建好。这也是「它就是 install」这句话的完整含义:它算,落盘交给上层。
  • 单文件上限 256 KiB —— 超了它拒算这个包的身份,而不是截断。静默截断会算出一个错的身份,那比报错坏得多。
  • 容量护栏会静默截断 —— 仓库 512 个实例、单包 use 边 2048 条、目录项 4096 条、依赖树深度 256。超出这些没有报错,属于已知上限,不是设计。
  • 没有注册中心服务端,Lompi 也不自己联网 —— 网址库就是一个 git 仓库(布局与 store 相同),没有服务端要运维。而 fetch 给出的是计划:PE 上那 8 个 syscall 里没有 socket,垫片也没有 ws2_32,所以网络那一步交给 git、由 shell 或 CI 执行。也不做二进制分发:泛型是类型定向单态化的,使用方必须看得到源码。
完整参考在文档里: 七个命令的逐条说明、两种 use 边的区别、退出码、报错对照,以及和 pip 的一张对照表,都在 Lompi · 包管理器。写库本身怎么开始,看 写一个 Loment 库