
从 uv、syft 和 just 出发,分别练习把版本约束解成一份锁文件、把目录里的组件认成一份 SBOM、把命令与跳过条件写成一份 justfile,并给出今晚能跑通的最小路径。
--exclude-newer 在一个更新的 wheel 已经缓存时也照常生效。1== 这种精确约束,再次是宽约束;同一档内按第一次出现的顺序挑,同样的输入就会解出同样的结果。3a 2 要 c 1、b 2 要 c 2 时,求解器记下「a 2 与 b 2 互相排斥」,退回上一步给 b 换一个版本再试。3numpy>=2,<3 ; python_version >= "3.11" 和 numpy>=1.16,<2 ; python_version < "3.11" 可以同时成立,一个包只允许装一个版本,于是一份解被拆成两支分别求解。3resolution-markers 把每支的环境记下来,下次解析沿用同一批分叉,结果才稳定。3uv.lock,靠 marker 决定装哪个版本。4uv add pandas httpx 之后,uv.lock 的开头是这样:version = 1
revision = 3
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.14' and sys_platform == 'win32'",
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version < '3.14' and sys_platform == 'win32'",
"python_full_version < '3.14' and sys_platform == 'emscripten'",
"python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]uv --version 直接打印 uv 0.13.2 (x86_64-unknown-linux-gnu)。不需要 Rust 工具链,也不用另装 Python。curl -LO https://github.com/astral-sh/uv/releases/download/0.13.2/uv-x86_64-unknown-linux-gnu.tar.gz
tar xzf uv-x86_64-unknown-linux-gnu.tar.gz
./uv-x86_64-unknown-linux-gnu/uv init --python 3.12 demo
cd demo
./uv-x86_64-unknown-linux-gnu/uv add pandas httpxuv add 会建好虚拟环境、解出整张依赖图、写好锁文件,再按锁文件安装。缓存暖起来之后,输出是这样的:Resolved 13 packages in 3ms
Installed 12 packages in 28ms
+ anyio==4.15.1
+ httpcore==1.0.9
+ httpx==0.28.1
+ numpy==2.5.4
+ pandas==3.0.6
...uv.lock 顶部的 resolution-markers,对照自己项目支持的 Python 版本,看有哪几块是这台机器永远用不到的。requirements.in 里写 httpx==0.28.1 和 httpcore==0.16.3,然后 uv pip compile requirements.in: error: No solution found when resolving dependencies
cause: Because httpx==0.28.1 depends on httpcore==1.* and you require httpx==0.28.1, we can conclude that you require httpcore==1.*.
And because you require httpcore==0.16.3, we can conclude that your requirements are unsatisfiable.flask>=2.0 和 werkzeug<2.1 写进同一份 requirements.in,本机解出 flask==2.1.3 与 werkzeug==2.0.3,用时 169 毫秒。最新的 flask 与 werkzeug<2.1 冲突,求解器退回到还能用的旧版本。
4. 验证自己写的版本下限。 只写 flask>=2.0 时默认解出 flask==3.1.3;加上 --resolution lowest 会解出 flask==2.0.0,连同 jinja2==3.0.0、click==7.1.2。项目声明的下限能不能真的跑,只有这一条命令会告诉你。4
5. 量一次安装。 同一组依赖(pandas 加 httpx),冷缓存下 uv sync 用了 1.556 秒;python -m venv 加 pip install --no-cache-dir 用了 6.575 秒。差值里有一部分是解析,有一部分是安装方式。--exclude-newer 的修复:旧的行为在某些缓存状态下会选出一个本该被排除的版本。1pyproject.toml 里加一条更严的要求,只要旧版本仍然满足,uv lock 就会保留旧版本;要换版本得显式加 --upgrade 或 --upgrade-package。4required-environments 收窄要支持的环境。4golang.org/x/net,其中一个标着 Critical。6*.dist-info/METADATA 的、读 uv.lock 的、读 go.mod 的、读 apk 与 dpkg 数据库的,每种认一类痕迹。8syft 输出 25 条记录,去重后只有 13 个包名:每个包都被 python-installed-package-cataloger 从已安装的 .dist-info/METADATA 认了一次,又被 python-package-cataloger 从 uv.lock 认了一次。pkg:pypi/pandas@3.0.6,来源写作 foundBy,位置写作 locations,里面同时列着 METADATA 与 RECORD 两个文件。-o cyclonedx-json@1.6。9--enrich 打开后才会从线上补信息。10curl -LO https://github.com/anchore/syft/releases/download/v1.55.0/syft_1.55.0_linux_amd64.tar.gz
tar xzf syft_1.55.0_linux_amd64.tar.gz syft
./syft versionApplication: syft
Version: 1.55.0
BuildDate: 2026-10-19T18:54:46Z
GoVersion: go1.26.8
SchemaVersion: 16.1.11NAME VERSION TYPE
anyio 4.15.1 python
certifi 2026.10.18 python
demo 0.1.0 python
h11 0.16.0 python
httpcore 1.0.9 python
httpx 0.28.1 python
idna 3.20 python
numpy 2.5.4 python
pandas 3.0.6 python
python-dateutil 2.9.0.post0 python
six 1.17.0 python
typing-extensions 4.16.0 python
tzdata 2026.5 python./syft dir:demo -o spdx-json=spdx.json -o cyclonedx-json=cdx.jsonfoundBy。 ./syft dir:demo -o json | jq -r '.artifacts[] | "\(.name) <- \(.foundBy)"' | sort,对照行数(25)与包名数(13),看清哪些包是被两条证据同时认出来的。./syft file:./syft 在本机列出 296 个 go-module,从 cel.dev/expr v0.25.2 到 github.com/... 一应俱全。证据是编译进二进制的 Go 构建信息,整个过程没有运行这个二进制。./syft file:./just 只输出一行 No packages discovered。Rust 二进制要被认出来,构建时得带上 cargo-auditable 那类信息,否则它是看不见的。./syft file:just-1.59.0-x86_64-unknown-linux-musl.tar.gz 能列出整排 rust-crate,来源是包里自带的 Cargo.lock。./syft dir:demo -o purls 每行一个 pkg:pypi/...,方便直接喂给漏洞扫描器。9golang.org/x/net,其中一个按发布说明标为 Critical。6--scope all-layers。10[cache(environment)] 用来指定哪些环境变量参与缓存键;--working-directory 可以不配 --justfile 单独使用,--usage 支持带模块路径。11a 与 b,得到 error: recipe 'b' has circular dependency 'a -> b -> a',还带着出错的行号。14just build 连跑两次,两次都重新编译;同一份 Makefile 第二次会打印「已是最新」。14[cache] 明确声明。 加上这个属性的脚本配方会按缓存键跳过,键是 BLAKE3 哈希,存在 justfile 旁边的 .justcache/ 里。14body、environment、executor、extension、extra、inputs、positional、recipe、working_directory;除了 extra 与 inputs,其余自动填。14set shell := ["bash", "-uc"]
target := "out"
# 编译出可执行文件
build:
mkdir -p {{target}}
cc main.c -o {{target}}/app
# 跑一遍程序
run: build
./{{target}}/app
# 只做检查,不产出
lint:
@echo "lint ok"just --list 里的说明:$ just --list
Available recipes:
build # 编译出可执行文件
lint # 只做检查,不产出
run # 跑一遍程序just --dry-run run 把将要执行的命令行按顺序列出来,一行也不动手;just --evaluate 只算变量:$ just --dry-run run
mkdir -p out
cc main.c -o out/app
./out/app
$ just --evaluate
target := "out"just build,每遍都会重新编译;再写一份等价的 Makefile 跑两遍,第二次只输出「已是最新」。时间戳判断和每次都跑,差别亲手看得见。14a: b 和 b: a,运行 just a,报错在建任何东西之前就出现了。14set unstable、[script]、[cache(inputs = "main.c", outputs = "out/app")] 三个开关,第一次运行编译并打印 compiled,第二次什么都不打印;改一行 main.c 再跑,又编译一次。加 -vv 能看到 cache hit, skipping invocation,.justcache/ 里留下两个以 BLAKE3 哈希命名的 .json。14#!/usr/bin/env python3 开头的配方会被写成临时文件、加上执行权限再执行。15 本机的默认临时目录不可执行,于是报 execution error: Permission denied (os error 13);用 --tempdir 指到可执行目录后正常输出 size: 15952 bytes。just --clean build 删掉这个配方的缓存条目;本机跑完打印 removed 2 cache entries,其他配方的缓存留着。14[cache] 仍是实验特性,要 set unstable 才生效,官方也明说它本身就脆弱。14[cache] 的 inputs 与 outputs。| 想把哪件事固定下来 | 仓库与本期信号 | 最小条件 | 第一条可观察路径 | 主要限制 |
|---|---|---|---|---|
| 依赖的确切版本,以及每个平台各装哪个 | uv · 0.13.2 北京时间 10 月 20 日清晨发布 | 一个静态二进制 | uv add 之后打开 uv.lock 数 resolution-markers | 锁文件里的偏好会压住新加的约束,要显式 --upgrade。(解析文档) |
| 磁盘上究竟装了什么,谁来自哪份证据 | syft · v1.55.0 北京时间 10 月 20 日凌晨发布 | 一个静态二进制,全程离线 | syft dir:. 之后数 foundBy 的重复 | 没有留下元数据的二进制会扫出空清单。(输出格式) |
| 一条命令按什么顺序跑,什么时候可以跳过 | just · 1.59.0 北京时间 10 月 15 日下午发布 | 一个静态二进制 | 连跑两次 just build,再加一次 [cache] | 缓存键不覆盖时间与系统环境,跳过是否安全要自己判断。(仓库说明) |
uv.lock 写死版本与哈希,SBOM 写清成分与来源,justfile 写清顺序与跳过条件。github.com
github.com
docs.astral.sh
docs.astral.sh
github.com
github.com
github.com
github.com
oss.anchore.com
oss.anchore.com
github.com
github.com
just.systems
github.com
just.systems
github.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.