#!/usr/bin/env bash
# The released Swift package as an application uses it (scripts/package-release.sh makes it):
#
# scripts/check-release-package.sh
[--local]
#
# holds wai-sink-swift-.zip and WaiFFI-.xcframework.zip. The package is
# unpacked into a new directory, and an application package beside it that depends on it by path
# is resolved and built by SwiftPM, which fetches the framework from the URL Package.swift names
# and checks it against the checksum Package.swift names; the application then runs, and must
# print the native sink's interface version through the package. Then the same, with one digit of
# the checksum changed: SwiftPM must refuse to resolve it, saying the checksum does not match.
# Both must hold.
#
# Without --local the framework comes from its URL, as an application gets it (run it after the
# release has the asset). With --local it comes from over loopback, through a SwiftPM mirror
# of that URL, so the package is checked before it is attached; the URL and the checksum are the
# ones the package names either way. Each run has its own SwiftPM cache, configuration and build
# directories, so nothing an earlier run fetched is reused.
set -euo pipefail
die() { echo "check-release-package: $*" >&2; exit 1; }
[ $# -ge 2 ] || { echo "usage: scripts/check-release-package.sh [--local]" >&2; exit 2; }
DIR=$(cd "$1" && pwd) V="$2" LOCAL="${3:-}"
PKG="$DIR/wai-sink-swift-$V.zip"
[ -f "$PKG" ] || die "no $PKG"
T=$(mktemp -d)
SERVER=""
cleanup() {
# wait gives the server's status, 143 for the TERM it was sent: under set -e, as the trap's
# last word it would become the script's.
if [ -n "$SERVER" ]; then kill "$SERVER" 2>/dev/null; wait "$SERVER" 2>/dev/null || true; fi
rm -rf "$T"
}
trap cleanup EXIT
ditto -x -k "$PKG" "$T/good"
[ -f "$T/good/wai-sink-swift/Package.swift" ] || die "$PKG holds no wai-sink-swift/Package.swift"
read -r URL SUM < <(python3 -I - "$T/good/wai-sink-swift/Package.swift" <<'PY'
import re, sys
m = re.search(r'let release: \(url: String, checksum: String\)\? = \(url: "([^"]+)", checksum: "([0-9a-f]{64})"\)', open(sys.argv[1]).read())
print(*(m.groups() if m else ("", "")))
PY
)
[ -n "$URL" ] || die "the package's Package.swift names no release URL and checksum"
echo "the package names $URL, checksum $SUM"
# The same package with the checksum's first digit changed.
mkdir -p "$T/bad"
cp -R "$T/good/wai-sink-swift" "$T/bad/"
BADSUM="$(printf '%x' $(( (0x${SUM:0:1} + 1) % 16 )))${SUM:1}"
sed -i '' "s/$SUM/$BADSUM/" "$T/bad/wai-sink-swift/Package.swift"
grep -q "$BADSUM" "$T/bad/wai-sink-swift/Package.swift" || die "the changed checksum was not written"
if [ "$LOCAL" = --local ]; then
ZIP="$DIR/$(basename "$URL")"
[ -f "$ZIP" ] || die "no $ZIP for the mirror"
PORT=$(python3 -I -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1])')
python3 -I -m http.server --bind 127.0.0.1 --directory "$DIR" "$PORT" > "$T/server.log" 2>&1 &
SERVER=$!
for _ in $(seq 1 50); do curl -fsS -o /dev/null "http://127.0.0.1:$PORT/$(basename "$URL")" 2>/dev/null && break; sleep 0.2; done
fi
# An application in /app that uses the package in /wai-sink-swift.
app() { #
local c="$T/$1"
mkdir -p "$c/app/Sources/consumer"
cat > "$c/app/Package.swift" <<'SWIFT'
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "consumer",
platforms: [.macOS(.v13)],
dependencies: [.package(path: "../wai-sink-swift")],
targets: [.executableTarget(name: "consumer", dependencies: [.product(name: "WaiSink", package: "wai-sink-swift")])]
)
SWIFT
cat > "$c/app/Sources/consumer/main.swift" <<'SWIFT'
import WaiSink
let abi = WaiSink.abiVersion
guard abi >= 1 else { fatalError("the native sink's interface version is \(abi)") }
print("WaiSink: the native sink's interface version \(abi), through the released WaiFFI.xcframework")
SWIFT
SPM=(--package-path "$c/app" --scratch-path "$c/build" --cache-path "$c/cache" --config-path "$c/config" --security-path "$c/security")
if [ -n "$SERVER" ]; then
swift package "${SPM[@]}" config set-mirror --original "$URL" --mirror "http://127.0.0.1:$PORT/$(basename "$URL")" >/dev/null
fi
}
app good
if ! swift build "${SPM[@]}" > "$T/good.log" 2>&1; then
tail -40 "$T/good.log" >&2
die "the package does not resolve and build from $URL with its checksum"
fi
out=$("$T/good/build/debug/consumer") || die "the application built against the package does not run"
echo "$out"
app bad
if swift package "${SPM[@]}" resolve > "$T/bad.log" 2>&1; then
die "SwiftPM resolved the package with a wrong checksum ($BADSUM)"
fi
grep -qi "checksum" "$T/bad.log" || { cat "$T/bad.log" >&2; die "SwiftPM refused the wrong checksum, but not for its checksum"; }
echo "a wrong checksum is refused: $(grep -i -m1 checksum "$T/bad.log" | sed 's/^[[:space:]]*//')"
echo "check-release-package: wai-sink-swift-$V resolves, builds and runs with its framework from $URL${SERVER:+ (served from $DIR through a mirror of it)}, and refuses a wrong checksum"