Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ request open with the pending version and changelog. Merging it sets the
version in `packages/zero-swift-cli/package.json` and `swift/VERSION`, and the
workflow then publishes the npm package and tags the SwiftPM mirror after
approval in the `swift-release` environment.

A change that breaks the Swift package's public API, so that an app building
against the last release no longer compiles, needs a `minor` changeset while
zero-swift is below 1.0, and a `major` one from 1.0. CI runs
`swift/scripts/api-breaks.sh`, which lists the breaks since the last release
and fails unless a changeset declares them.
3 changes: 3 additions & 0 deletions .github/workflows/swift-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
run: swift test
- name: Docs
run: scripts/docs.sh
# The Apple-only API, which Linux doesn't build.
- name: API breaks
run: scripts/api-breaks.sh
- name: Test (iOS Simulator)
run: |
device="$(xcrun simctl list devices available -j |
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
name: generated-vectors
path: packages/zero-conformance/generated/
- name: Install system libraries
run: apt-get update && apt-get install -y --no-install-recommends libsqlite3-dev
run: apt-get update && apt-get install -y --no-install-recommends libsqlite3-dev jq
- name: Format
run: swift format lint --strict --recursive Sources Tests Package.swift
- name: Build
Expand All @@ -84,6 +84,12 @@ jobs:
# Apple-only; swift-macos.yml checks it.
- name: Docs
run: scripts/docs.sh Zero ZeroTesting
# Fails on a public API break since the last release that no changeset
# declares. The checkout belongs to another user in the container.
- name: API breaks
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
scripts/api-breaks.sh
# zbugs' Swift port, checked against zbugs' TS queries and mutators on
# the samples in drift-samples.ts (the vectors job checks the record is
# current).
Expand Down
107 changes: 107 additions & 0 deletions swift/scripts/api-breaks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/bin/sh
# Fails when the public API breaks compared with the last release, unless the
# break is declared: a pending changeset, or the version being released, bumps
# zero-swift enough for it (a minor below 1.0, a major from 1.0).
#
# scripts/api-breaks.sh [<baseline tag>] default: the mirror's newest tag
#
# The digester (`swift package diagnose-api-breaking-changes`) needs the
# package at the root of a git repository, so this runs it in a scratch
# repository shaped like the SwiftPM mirror (wevm/zero-swift): the released tag,
# with this checkout's swift/ committed on top. Its API dumps also hold
# `package` declarations, which apps can't use, so they're removed from both
# sides before comparing. On Linux the Apple-only API (ZeroUI, most of
# ZeroVirtual) isn't built, so swift-macos.yml runs this too.
set -eu
cd "$(dirname "$0")/.."
swift=$(pwd)
root=$(cd .. && pwd)
mirror=https://github.com/wevm/zero-swift.git
products="Zero ZeroUI ZeroVirtual ZeroTesting"

baseline=${1:-$(git ls-remote --tags --refs "$mirror" | sed 's|.*refs/tags/||' |
sort -V | tail -1)}

work=$(mktemp -d)
dumps=$(mktemp -d)
trap 'rm -rf "$work" "$dumps"' EXIT

git -C "$work" init -q
git -C "$work" fetch -q --depth 1 "$mirror" "refs/tags/$baseline:refs/tags/$baseline"
git -C "$work" checkout -q "$baseline"
git -C "$work" rm -rq .
# This checkout's swift/, with uncommitted changes, as the release would have it.
git ls-files -z --cached --others --exclude-standard |
tar --null -cf - -T - | tar -xf - -C "$work"
git -C "$work" add -A
git -C "$work" -c user.name=api-breaks -c user.email=api-breaks@localhost \
commit -qm current --allow-empty

digester=$(command -v swift-api-digester || xcrun --find swift-api-digester 2>/dev/null ||
echo "$(dirname "$(readlink -f "$(command -v swift)")")/swift-api-digester")

cd "$work"
# Dumps each side's API, keeps only its public declarations, and compares
# them. Stripping `package` declarations from both sides matters: one that
# was public and became `package` reads as removed.
for side in old:$baseline new:HEAD; do
name=${side%%:*} rev=${side#*:}
swift package diagnose-api-breaking-changes "$rev" --products $products \
--baseline-dir "$dumps/$name" >"$dumps/$name.log" 2>&1 || true
if ! ls "$dumps/$name"/*/*.json >/dev/null 2>&1; then
echo "Couldn't dump the API at $rev:" >&2
cat "$dumps/$name.log" >&2
exit 1
fi
for dump in "$dumps/$name"/*/*.json; do
jq 'walk(if type == "object" and has("children")
then .children |= map(select(.isInternal != true)) else . end)' \
"$dump" >"$dumps/$name-$(basename "$dump")"
done
done
breaks=
for old in "$dumps"/old-*.json; do
module=$(basename "$old" .json) module=${module#old-}
new="$dumps/new-$module.json"
if [ ! -e "$new" ]; then
breaks="$breaks$module: module has been removed
"
continue
fi
"$digester" -diagnose-sdk -print-module -input-paths "$old" -input-paths "$new" \
-o "$dumps/$module.txt"
breaks="$breaks$(grep -v -e '^/\*' -e '^$' "$dumps/$module.txt" || true)
"
done
breaks=$(printf '%s' "$breaks" | sed '/^$/d')
if [ -z "$breaks" ]; then
echo "No public API breaks since $baseline."
exit 0
fi

echo "Public API breaks since $baseline:"
printf '%s\n' "$breaks" | sed 's/^/ /'
case $baseline in
0.*) needed=minor levels='minor|major' ;;
*) needed=major levels=major ;;
esac
# A pending changeset that declares the break.
if grep -lqE "^['\"]zero-swift['\"]: *($levels)\$" "$root"/.changeset/*.md 2>/dev/null; then
echo "Allowed: a pending changeset bumps zero-swift by a $needed or more."
exit 0
fi
# The version pull request, or main after it merged: the changesets are gone,
# and the version being released declares the break.
version=$(cat "$swift/VERSION")
old_major=${baseline%%.*} old_minor=${baseline#*.} old_minor=${old_minor%%.*}
new_major=${version%%.*} new_minor=${version#*.} new_minor=${new_minor%%.*}
if [ "$new_major" -gt "$old_major" ] ||
{ [ "$needed" = minor ] && [ "$new_major" -eq "$old_major" ] &&
[ "$new_minor" -gt "$old_minor" ]; }; then
echo "Allowed: $version is a $needed or more after $baseline."
exit 0
fi
echo "These break apps that build against zero-swift $baseline. Keep the old" \
"API (deprecate it), or add a changeset that bumps zero-swift by a $needed" \
"and tells apps what to change." >&2
exit 1
Loading