Files
mail/scripts/blitz-patches.sh

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