mirror of
https://github.com/flectar/mail.git
synced 2026-11-11 17:57:54 +00:00
297 lines
10 KiB
Bash
Executable File
297 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
PATCH_DIR="$REPO_ROOT/patches/blitz"
|
|
METADATA_FILE="$PATCH_DIR/upstream.toml"
|
|
SERIES_FILE="$PATCH_DIR/series"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
scripts/blitz-patches.sh verify
|
|
scripts/blitz-patches.sh stage <version>
|
|
|
|
verify downloads the pinned crates, applies the patch queue in a temporary
|
|
directory, and checks that the result exactly matches crates/blitz-*.
|
|
|
|
stage prepares pristine and patched trees for a new crates.io version. It does
|
|
not modify the repository. If a patch conflicts, the staged tree and reject
|
|
files are retained for manual resolution.
|
|
EOF
|
|
}
|
|
|
|
metadata_value() {
|
|
local key=$1
|
|
sed -n "s/^${key} = \"\([^\"]*\)\"$/\1/p" "$METADATA_FILE"
|
|
}
|
|
|
|
metadata_checksum() {
|
|
local crate=$1
|
|
awk -v section="[crates.${crate}]" '
|
|
$0 == section { in_section = 1; next }
|
|
/^\[/ { in_section = 0 }
|
|
in_section && /^sha256 = / {
|
|
value = $0
|
|
sub(/^sha256 = "/, "", value)
|
|
sub(/"$/, "", value)
|
|
print value
|
|
exit
|
|
}
|
|
' "$METADATA_FILE"
|
|
}
|
|
|
|
sha256_file() {
|
|
local path=$1
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$path" | awk '{ print $1 }'
|
|
else
|
|
shasum -a 256 "$path" | awk '{ print $1 }'
|
|
fi
|
|
}
|
|
|
|
registry_checksum() {
|
|
local crate=$1
|
|
local version=$2
|
|
local record
|
|
record=$(curl --fail --silent --show-error --location --retry 3 \
|
|
"https://index.crates.io/bl/it/${crate}" \
|
|
| { grep -F "\"vers\":\"${version}\"" || true; } \
|
|
| tail -n 1)
|
|
if [[ -z "$record" ]]; then
|
|
printf 'No crates.io release found for %s %s\n' "$crate" "$version" >&2
|
|
return 1
|
|
fi
|
|
if [[ "$record" == *'"yanked":true'* ]]; then
|
|
printf 'Refusing to stage yanked release %s %s\n' "$crate" "$version" >&2
|
|
return 1
|
|
fi
|
|
printf '%s\n' "$record" | sed -n 's/.*"cksum":"\([0-9a-f]*\)".*/\1/p'
|
|
}
|
|
|
|
download_and_extract() {
|
|
local crate=$1
|
|
local version=$2
|
|
local expected_checksum=$3
|
|
local work_root=$4
|
|
local archive="$work_root/${crate}-${version}.crate"
|
|
local destination="$work_root/upstream/crates/${crate}"
|
|
local actual_checksum
|
|
|
|
curl --fail --silent --show-error --location --retry 3 \
|
|
--output "$archive" \
|
|
"https://static.crates.io/crates/${crate}/${crate}-${version}.crate"
|
|
actual_checksum=$(sha256_file "$archive")
|
|
if [[ "$actual_checksum" != "$expected_checksum" ]]; then
|
|
printf 'Checksum mismatch for %s %s\nexpected: %s\nactual: %s\n' \
|
|
"$crate" "$version" "$expected_checksum" "$actual_checksum" >&2
|
|
return 1
|
|
fi
|
|
|
|
mkdir -p "$destination"
|
|
tar -xzf "$archive" -C "$destination" --strip-components=1
|
|
}
|
|
|
|
apply_series() {
|
|
local patched_root=$1
|
|
local retain_rejects=$2
|
|
local patch
|
|
|
|
while IFS= read -r patch || [[ -n "$patch" ]]; do
|
|
[[ -z "$patch" || "$patch" == \#* ]] && continue
|
|
if ! git -C "$patched_root" apply --check "$PATCH_DIR/$patch"; then
|
|
printf 'Patch does not apply: %s\n' "$patch" >&2
|
|
if [[ "$retain_rejects" == true ]]; then
|
|
git -C "$patched_root" apply --reject "$PATCH_DIR/$patch" || true
|
|
printf 'Resolve the .rej files under %s and regenerate this patch.\n' \
|
|
"$patched_root" >&2
|
|
fi
|
|
return 1
|
|
fi
|
|
git -C "$patched_root" apply "$PATCH_DIR/$patch"
|
|
done < "$SERIES_FILE"
|
|
}
|
|
|
|
prepare_trees() {
|
|
local version=$1
|
|
local mode=$2
|
|
local work_root=$3
|
|
local crate
|
|
local checksum
|
|
|
|
mkdir -p "$work_root/upstream/crates" "$work_root/patched/crates"
|
|
for crate in blitz-dom blitz-paint; do
|
|
if [[ "$mode" == verify ]]; then
|
|
checksum=$(metadata_checksum "$crate")
|
|
else
|
|
checksum=$(registry_checksum "$crate" "$version")
|
|
fi
|
|
if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then
|
|
printf 'Invalid or missing checksum for %s: %s\n' "$crate" "$checksum" >&2
|
|
return 1
|
|
fi
|
|
printf '%s %s: %s\n' "$crate" "$version" "$checksum"
|
|
download_and_extract "$crate" "$version" "$checksum" "$work_root"
|
|
done
|
|
|
|
cp -R "$work_root/upstream/crates/." "$work_root/patched/crates/"
|
|
}
|
|
|
|
verify_revision() {
|
|
local upstream_root=$1
|
|
local expected_revision=$2
|
|
local crate
|
|
local actual_revision
|
|
|
|
for crate in blitz-dom blitz-paint; do
|
|
actual_revision=$(awk -F'"' '/"sha1"/ { print $4; exit }' \
|
|
"$upstream_root/crates/$crate/.cargo_vcs_info.json")
|
|
if [[ "$actual_revision" != "$expected_revision" ]]; then
|
|
printf 'Upstream revision mismatch for %s: expected %s, got %s\n' \
|
|
"$crate" "$expected_revision" "$actual_revision" >&2
|
|
return 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
verify_tree() {
|
|
local patched_root=$1
|
|
local crate
|
|
local failed=false
|
|
|
|
for crate in blitz-dom blitz-paint; do
|
|
if ! diff -qr "$patched_root/crates/$crate" "$REPO_ROOT/crates/$crate"; then
|
|
printf 'Checked-in %s differs from the documented patch result.\n' "$crate" >&2
|
|
git diff --no-index --stat \
|
|
"$patched_root/crates/$crate" "$REPO_ROOT/crates/$crate" || true
|
|
failed=true
|
|
fi
|
|
done
|
|
[[ "$failed" == false ]]
|
|
}
|
|
|
|
verify_manifest_wiring() {
|
|
local version=$1
|
|
local crate
|
|
local requirement
|
|
|
|
for crate in blitz-dom blitz-html blitz-paint blitz-traits; do
|
|
requirement="${crate} = \"=${version}\""
|
|
if [[ "$crate" == blitz-dom ]]; then
|
|
requirement="${crate} = { version = \"=${version}\", features = [\"floats\"] }"
|
|
fi
|
|
if ! grep -Fqx "$requirement" "$REPO_ROOT/Cargo.toml"; then
|
|
printf 'Root Cargo.toml must contain: %s\n' "$requirement" >&2
|
|
return 1
|
|
fi
|
|
done
|
|
for requirement in \
|
|
'blitz-dom = { path = "crates/blitz-dom" }' \
|
|
'blitz-paint = { path = "crates/blitz-paint" }'; do
|
|
if ! grep -Fqx "$requirement" "$REPO_ROOT/Cargo.toml"; then
|
|
printf 'Root Cargo.toml must contain: %s\n' "$requirement" >&2
|
|
return 1
|
|
fi
|
|
done
|
|
for manifest in platform/android/Cargo.toml platform/ohos/Cargo.toml; do
|
|
for requirement in \
|
|
'blitz-dom = { path = "../../crates/blitz-dom" }' \
|
|
'blitz-paint = { path = "../../crates/blitz-paint" }'; do
|
|
if ! grep -Fqx "$requirement" "$REPO_ROOT/$manifest"; then
|
|
printf '%s must contain: %s\n' "$manifest" "$requirement" >&2
|
|
return 1
|
|
fi
|
|
done
|
|
done
|
|
|
|
# The benchmark is part of the renderer's compatibility surface too, and
|
|
# Android and OpenHarmony resolve separate graphs. Do not let any drift.
|
|
python3 - "$REPO_ROOT" "$version" <<'PY'
|
|
from pathlib import Path
|
|
import json
|
|
import sys
|
|
import tomllib
|
|
|
|
root, version = Path(sys.argv[1]), sys.argv[2]
|
|
crates = ("blitz-dom", "blitz-html", "blitz-paint", "blitz-traits")
|
|
for manifest in ("Cargo.toml", "crates/renderer-probe/Cargo.toml"):
|
|
dependencies = tomllib.loads((root / manifest).read_text())["dependencies"]
|
|
for crate in crates:
|
|
requirement = dependencies[crate]
|
|
actual = requirement if isinstance(requirement, str) else requirement["version"]
|
|
if actual != f"={version}":
|
|
raise SystemExit(f"{manifest}: {crate} must pin ={version}, got {actual}")
|
|
if "floats" not in dependencies["blitz-dom"].get("features", []):
|
|
raise SystemExit(f"{manifest}: email layout requires Blitz floats")
|
|
|
|
for lockfile in ("Cargo.lock", "platform/android/Cargo.lock", "platform/ohos/Cargo.lock"):
|
|
packages = tomllib.loads((root / lockfile).read_text())["package"]
|
|
for crate in (*crates, "stylo_taffy"):
|
|
matches = [package for package in packages if package["name"] == crate]
|
|
if len(matches) != 1 or matches[0]["version"] != version:
|
|
raise SystemExit(f"{lockfile}: expected exactly one {crate} {version}")
|
|
local = crate in ("blitz-dom", "blitz-paint")
|
|
if ("source" not in matches[0]) != local:
|
|
raise SystemExit(f"{lockfile}: incorrect patch wiring for {crate}")
|
|
|
|
packages = tomllib.loads((root / "Cargo.lock").read_text())["package"]
|
|
expected = {
|
|
f"cargo/vendor/{p['name']}-{p['version']}": p["checksum"]
|
|
for p in packages if p.get("source", "").startswith("registry+")
|
|
}
|
|
sources = json.loads((root / "packaging/flatpak/cargo-sources.json").read_text())
|
|
archives = {s["dest"]: s["sha256"] for s in sources if s["type"] == "archive"}
|
|
checksums = {
|
|
s["dest"]: json.loads(s["contents"])["package"]
|
|
for s in sources if s.get("dest-filename") == ".cargo-checksum.json"
|
|
}
|
|
if archives != expected or checksums != expected:
|
|
raise SystemExit("Flatpak offline sources differ from Cargo.lock")
|
|
PY
|
|
}
|
|
|
|
command=${1:-verify}
|
|
case "$command" in
|
|
verify)
|
|
[[ $# -eq 1 ]] || { usage >&2; exit 2; }
|
|
version=$(metadata_value version)
|
|
revision=$(metadata_value revision)
|
|
if [[ -z "$version" || ! "$revision" =~ ^[0-9a-f]{40}$ ]]; then
|
|
printf 'Invalid metadata in %s\n' "$METADATA_FILE" >&2
|
|
exit 1
|
|
fi
|
|
verify_manifest_wiring "$version"
|
|
work_root=$(mktemp -d "${TMPDIR:-/tmp}/flectar-blitz-verify.XXXXXX")
|
|
trap 'rm -rf -- "$work_root"' EXIT
|
|
prepare_trees "$version" verify "$work_root"
|
|
verify_revision "$work_root/upstream" "$revision"
|
|
apply_series "$work_root/patched" false
|
|
verify_tree "$work_root/patched"
|
|
printf 'Blitz %s patch queue is reproducible.\n' "$version"
|
|
;;
|
|
stage)
|
|
[[ $# -eq 2 ]] || { usage >&2; exit 2; }
|
|
version=$2
|
|
if [[ ! "$version" =~ ^[0-9A-Za-z.+-]+$ ]]; then
|
|
printf 'Invalid crate version: %s\n' "$version" >&2
|
|
exit 2
|
|
fi
|
|
work_root=$(mktemp -d "${TMPDIR:-/tmp}/flectar-blitz-stage.XXXXXX")
|
|
printf 'Staging Blitz %s in %s\n' "$version" "$work_root"
|
|
prepare_trees "$version" stage "$work_root"
|
|
if ! apply_series "$work_root/patched" true; then
|
|
printf 'Staged trees retained at %s\n' "$work_root" >&2
|
|
exit 1
|
|
fi
|
|
printf 'All existing patches applied. Staged trees retained at %s\n' "$work_root"
|
|
;;
|
|
-h|--help|help)
|
|
usage
|
|
;;
|
|
*)
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|