#!/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"