Files
Genarrative/rust/scripts/check-package-manifests.sh
kdletters 202279c6d9 新增独立 Agent Runtime Rust 工作区
新增 Core、Engine、Runtime、SQLite、Provider、MCP、Skill、Codex、CLI 与 DAG crate

补齐 OpenAI endpoint 配置、Provider 实例/协议路由和统一工具权限边界

加入持久化、lease、checkpoint、reconciliation、审批恢复与消息历史回归

加入独立 workspace CI、依赖边界、能力集和 Fake Agent 测试脚本

同步建设计划、TODO、架构、测试与验收文档
2026-09-06 17:44:54 +08:00

180 lines
7.0 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
# 只做本地发布前预检:验证 package 元数据、workspace 内部依赖版本和
# `cargo package --list` 文件边界。它不访问 registry、不上传 crate,也不把
# 尚未发布的内部依赖伪装成已经可安装的包。
usage() {
cat <<'EOF'
用法:
./scripts/check-package-manifests.sh [workspace/Cargo.toml]
检查内容:
- 所有 workspace package 都有版本、Rust edition、最低 Rust 版本、license 和 description
- path dependency 只能指向 workspace 内部,并且带明确版本要求;
- 每个 package 的 cargo package 文件清单不包含 target、.git、数据库或环境文件。
该检查使用 `cargo package --offline --list --no-verify`,只验证本地 manifest
和打包边界;未显式设置 `CARGO_TARGET_DIR` 时,中间产物会放在
`~/data/tmp`(可用 `AGENT_PACKAGE_TMPDIR` 覆盖)并在退出时清理。依赖其它内部
crate 的完整 package 校验仍需目标 registry 先按依赖顺序发布这些 crate。
EOF
}
if (($# == 1)) && [[ "$1" == "-h" || "$1" == "--help" ]]; then
usage
exit 0
fi
if (($# > 1)); then
usage >&2
exit 2
fi
caller_pwd="$(pwd -P)"
script_path="${BASH_SOURCE[0]}"
case "$script_path" in
/*) ;;
*) script_path="$caller_pwd/$script_path" ;;
esac
script_dir="$(cd -- "$(dirname -- "$script_path")" && pwd -P)"
script_workspace_root="$(cd -- "$script_dir/.." && pwd -P)"
manifest_input="${1:-}"
if [[ -z "$manifest_input" ]]; then
workspace_manifest="$script_workspace_root/Cargo.toml"
elif [[ "$manifest_input" == /* ]]; then
workspace_manifest="$manifest_input"
elif [[ -f "$caller_pwd/$manifest_input" ]]; then
workspace_manifest="$caller_pwd/$manifest_input"
else
workspace_manifest="$script_workspace_root/$manifest_input"
fi
manifest_dir="$(cd -- "$(dirname -- "$workspace_manifest")" && pwd -P)"
workspace_manifest="$manifest_dir/$(basename -- "$workspace_manifest")"
workspace_root="$manifest_dir"
# `cargo package --list` 仍可能为 workspace 生成 target/package 中间文件。
# 未显式指定 CARGO_TARGET_DIR 时,把它放到本轮临时目录并在退出时清理,
# 避免 manifest 预检污染仓库;调用方显式指定时则保留其生命周期和位置。
package_target_dir_owned=0
if [[ -z "${CARGO_TARGET_DIR:-}" ]]; then
package_tmp_parent="${AGENT_PACKAGE_TMPDIR:-${TMPDIR:-${HOME:?HOME 未设置}/data/tmp}}"
mkdir -p -- "$package_tmp_parent"
package_target_dir="$(mktemp -d "${package_tmp_parent%/}/agent-package-manifests.XXXXXX")"
export CARGO_TARGET_DIR="$package_target_dir"
package_target_dir_owned=1
fi
cleanup_package_target() {
if (( package_target_dir_owned == 1 )) && [[ -n "${package_target_dir:-}" ]] &&
[[ -d "$package_target_dir" ]]; then
rm -r -- "$package_target_dir"
fi
}
trap cleanup_package_target EXIT
[[ -r "$workspace_manifest" ]] || {
echo "workspace manifest 不存在或不可读:$workspace_manifest" >&2
exit 1
}
grep -Eq '^[[:space:]]*\[workspace\][[:space:]]*$' "$workspace_manifest" || {
echo "Cargo.toml 不是 workspace manifest$workspace_manifest" >&2
exit 1
}
metadata="$(cargo metadata --offline --locked --manifest-path "$workspace_manifest" \
--format-version 1 --no-deps)"
# Python 负责解析 Cargo metadata,避免用 grep 解析 TOML/JSON 时误判依赖。
# 使用命令替换而不是 process substitution,确保 Python 校验失败时不会被
# `mapfile` 吞掉退出码,也不会拿着不完整的名称列表继续打包检查。
package_names_text="$(printf '%s\n' "$metadata" | \
WORKSPACE_ROOT="$workspace_root" python3 -c '
import json
import os
import pathlib
import sys
root = pathlib.Path(os.environ["WORKSPACE_ROOT"]).resolve()
metadata = json.load(sys.stdin)
packages = metadata.get("packages", [])
workspace_ids = set(metadata.get("workspace_members", []))
workspace_packages = [p for p in packages if p.get("id") in workspace_ids]
if not workspace_packages:
raise SystemExit("workspace 没有可检查的 package")
if len(workspace_packages) != len(workspace_ids):
raise SystemExit("workspace_members 与 package metadata 数量不一致")
names = {p.get("name") for p in workspace_packages}
if len(names) != len(workspace_packages):
raise SystemExit("workspace package 名称重复")
for package in sorted(workspace_packages, key=lambda item: item["name"]):
name = package.get("name", "")
if not name.strip():
raise SystemExit("workspace package 缺少名称")
manifest = pathlib.Path(package["manifest_path"]).resolve()
if root not in manifest.parents or manifest == root / "Cargo.toml":
raise SystemExit(f"package manifest 不在 crates/ 下:{manifest}")
for field in ("version", "edition", "rust_version", "license", "description"):
if not str(package.get(field) or "").strip():
raise SystemExit(f"{name} 缺少 package.{field}")
if package["edition"] != "2024":
raise SystemExit(f"{name} 使用了非 Rust 2024 edition{package['edition']}")
if package["rust_version"] != "1.96":
raise SystemExit(f"{name} 的 rust-version 不是 1.96{package['rust_version']}")
for dependency in package.get("dependencies", []):
if dependency.get("source") is not None:
continue
dependency_name = dependency.get("name", "")
dependency_path = dependency.get("path")
if dependency_name not in names:
raise SystemExit(
f"path dependency 指向 workspace 外部:{name} -> {dependency_name}"
)
if not dependency_path or root not in pathlib.Path(dependency_path).resolve().parents:
raise SystemExit(
f"path dependency 路径越过 workspace 边界:{name} -> {dependency_name}"
)
requirement = str(dependency.get("req") or "").strip()
if requirement in ("", "*", "latest"):
raise SystemExit(
f"path dependency 缺少明确版本要求:{name} -> {dependency_name}"
)
print(name)
')" || exit 1
[[ -n "$package_names_text" ]] || {
echo "没有找到 workspace package" >&2
exit 1
}
mapfile -t package_names <<<"$package_names_text"
for package in "${package_names[@]}"; do
package_files="$(cargo package --offline --locked --manifest-path "$workspace_manifest" \
-p "$package" --allow-dirty --no-verify --list 2>/dev/null)" || {
echo "cargo package 文件清单失败:$package" >&2
exit 1
}
grep -Fxq 'Cargo.toml' <<<"$package_files" || {
echo "package 缺少 Cargo.toml$package" >&2
exit 1
}
grep -Eq '(^|/)src/' <<<"$package_files" || {
echo "package 缺少 src 文件:$package" >&2
exit 1
}
if grep -Eiq '(^|/)(\.git|target|\.env|.*\.db([.-]|$)|.*\.sqlite([.-]|$))' \
<<<"$package_files"; then
echo "package 清单包含不应发布的文件:$package" >&2
exit 1
fi
echo "package manifest passed: $package"
done
echo "package manifest check passed: ${#package_names[@]} packages"